Command Centre REST API
v2.0https://127.0.0.1:8904This document describes how you can use the Command Centre REST API to view and manage its configuration, user directory, events, and alarms.
Forward compatibility (HATEOAS)
This is a self-referencing REST API that follows the principles of HATEOAS. Other than the
initial GET /api when it first connects, your source code should not contain any URLs,
as they are subject to change. You should append the query parameters this document describes
for operations such as filtering and searching, but everying in the path should come from the
results of /api or pages linked from it.
/api only shows the API calls the server is licensed for.
Be prepared to append query parameters to URLs that already have their own: do not assume that you can simply add a question mark and your parameters.
Forward compatibility (undocumented features)
Any behaviour of that is not referenced in this documentation is subject to change. This includes routes (URLs), default values, request parameters, and HTTP verbs, among others.
In particular:
If you discover a route or query parameter that is not documented here, a future version may change its behaviour.
Take all 2xx responses as success and all 4xx responses as failure. Example: a success could be 200 or 204 depending on whether it has something to tell you, and a negative result will change between the various 400s depending on the environment.
Treat items IDs as strings even though they are currently look like small integers. We intend to change them.
Treat string identifiers as strings even if they look like GUIDs. If the documentation does not say they will always be a GUID, they may not.
Some routes work with a PATCH even though they should only accept a POST. One day, PATCHes to those routes will start to fail.
Format all the timestamps you send as specified in the documentation. The parser we currently use is quite flexible so you may find that other formats ( "1/12/2025" ) work today, but they may not tomorrow.
Access control
Clients authenticate by including a pre-shared API key in the Authorization header of each
request. Command Centre generates a random API key when you create an endpoint for your clients
to connect to. Search the Configuration Client online help for 'REST API' for how do do that.
In current versions of Command Centre, the API key will be in the format
XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX with each X an uppercase hexadecimal digit.
Future versions of Command Centre may use other formats of API key, so treat it as an opaque
string. Tempting as it may be, do not interpret is as a GUID.
There are two ways you can pass the API key to the server: following an authorisation method
of GGL-API-KEY, or (in 9.0 and later) in the style of HTTP Basic authentication: prefixed
with a colon, Base64-encoded, and following an authorisation method of Basic.
Examples:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
Early versions of Command Centre allowed you to omit the 'GGL-API-KEY' from the first form. Future versions of Command Centre and all versions of the API gateway will reject you with a 401 if you do that. The API gateway is especially fussy about case and punctuation.
HTTP Basic authentication
The two example headers above are equivalent: prepending a colon to the API key beginning
with C774 then encoding it into Base64 produces the 56 characters beginning with OkM3.
According to the HTTP Basic specification, the colon is a separator between a username and a password. In our case, the API key is the password and the username is unnecessary. If you place a username before the colon, before Base64-encoding it, Command Centre will ignore it and authenticate the connection based on the API key alone.
Pinning a client TLS certificate
Depending on Command Centre's site configuration, its REST API may also require a client certificate with each request. In versions up to and including 8.40 this was controlled by a flag labelled 'Do not require pinned client certficates' on the 'Web Services' tab of the server properties. In 8.50 that flag's label changed to 'Enable REST Clients with no client certificate', and its behaviour changed slightly when turned on.
If off, for all versions of CC, an incoming request's certificate has to match the thumbprint on its API key's REST Client item, and REST Client items with a blank thumbprint field do not work at all. This is how it ships, and it is the recommended way of running a production server.
If 'Do not require pinned client certificates' was turned on in versions up to and including 8.40, the server did not check any client certificates. If that flag is turned on in 8.50, now relabelled 'Enable REST Clients with no client certificate', it still does not check the client certificate if the thumbprint field of the matching REST Client item is blank, but if the Client item has a thumbprint, the server will reject connections with the wrong certificate.
See the Configuration Client help for instructions on where to enter REST Client thumbprints.
Source IP filtering
Also note that if IP filtering is enabled on the REST Client item in Command Centre, the API will only accept connections from the IP address ranges configurated into that client item.
Symptoms of authentication failures
If a connection attempt fails, the server will return a 401 and raise an event containing its reason for refusing the request. If that happens too often, the server will stop reporting each offence and will instead create a summary alarm at a much lower rate. The details of the alarm tell you how many failed attempts there have been since the start of the flood. The server will stay in this mode of reduced reporting until several minutes pass without a failed connection attempt.
The current failure limit is ten errors inside one minute. After that you will receive one "a large volume of requests has been denied" alarm every minute while the failures continue until five minutes passes without a failure.
The most common queries we receive from our integrators relate to their certificate handling. If your client's HTTP client library complains about certificates the first thing to check is Command Centre's alarm list. If there are 'invalid client certificate' alarms there, your client is not sending the certificate CC is expecting. If there are no alarms then the client is most likely rejecting the server's certificate.
Operator privileges
First, some background on operators, operator groups, and divisions.
To determine your REST client's privileges, the server starts by searching the list of REST
Client items in its configuration for the one with the API key in the Authorization header of
your request. Assuming it finds one, it takes the cardholder from that REST Client item.
To be any use at all that cardholder must be a member of at least one operator group. Being in an operator group makes a cardholder an operator.
After the server has your operator, it needs to check whether that operator has access to the items or events in the request. To explain that, it is necessary to cover divisions.
Aside from some special items such as day categories, every item in Command Centre is in a division. Divisions are hierarchical, with all divisions (but the root) being a child of another. Multi-server installations have one division tree per server.
Every event and alarm has a division. It is usually the division of the source item. Card events or 'forced door' alarms, for example, typically use a door as a source item; when an operator modifies an item, the event recording that change uses that operator's workstation as the source item; the alarm that the server generates when you send a bad API key uses the server item as the source.
An event's division is not always its source's division. 'Card activated' events have the server as a source item, but take on the cardholder's division so that operators who can see the cardholder can also see when the server activates their cards.
To link privileges to items and events, an operator group contains a list of privileges and a list of divisions. Its operators enjoy those privileges on all the items, events, and alarms that are in those divisions.
Having a privilege on a division also grants an operator that privilege on that division's descendants. Therefore an operator with a privilege on the root division has that privilege on all that server's items and events.
A common misconception is that the division an operator is in, or the division an operator group is in, have a bearing on the operator's privileges. They do not. It is all about the divisions in the operator group's 'Operator privileges' list.
If your privileges do not seem to be working
First, to protect Command Centre from accident and malice, you should strive to grant your REST clients the fewest and lowest-level privileges you can. Do not give them 'advanced user'. Reaching a point where you do not receive the results you want is a good thing: it means you have screwed things down a little too tightly.
If changing privileges in Command Centre does not seem to make any difference, remember to push the 'Refresh operator privileges' button in the properties window of your REST Client item.
Here are some general rules that may help if you are still not receiving the results you expect.
Receiving a 403 from a GET means the server may not have a license for the retrieval you are attempting or your operator does not have the privilege to view that object. If it is a licensing problem, the body of the response will describe it.
Receiving a 403 from a PATCH, POST, or DELETE means your operator does not have the necessary privilege to perform that operation. You need one of the privileges beginning with 'Edit', 'Modify', or 'Create'.
Receiving a 404 when trying to get one item or event means it either does not exist or your operator does not have the privilege to view it.
Receiving a 200 and an empty result set from a search means you are licensed for that search but your operator could not see any items or events that matched. One possible cause is that your operator is not licensed to see any items or events of that kind. Make sure that one of the operator groups that your operator is in has a 'View' privilege such as 'View events' or 'View cardholders' - whatever is appropriate. If it does, and you have hit the button mentioned above, then check that the divisions on the operator group contain the items you expect. If it is events you are searching for, check the source item on those events either by using a REST operator with 'View events' on the root division or by looking at them in one of the thick clients.
Text encoding
Command Centre's REST API encodes all payloads using UTF-8, and expects clients to do the same. It does not escape special characters in response bodies except where required to embed them in JSON.
Specifically, it does not sanitise HTML, XML, or SQL. Your clients should expect to receive strings exactly as they were sent, even if they were sent by a hostile client. Write your clients to resist injection attacks.
Dates and times
When you send dates and times to Command Centre you must use an ISO-8601 calendar date, time,
and time zone designator, with hyphens separating the date fields and colons separating the time
fields. This looks like YYYY-MM-DDTHH:MM:SSZ, where T is an actual T, and Z is a timezone
designator such as 'Z' for UTC or '-0800' for Pacific Standard Time. Specify up to seven
decimals on the seconds if you wish, but be aware that some Command Centre fields will not use
them. You must supply the year and month but omit the other fields as you like. Command Centre
will assume sensible defaults, such as the top of the minute when you omit seconds, midnight
when you omit the time, or first of the month when you omit the day.
Do not omit the timezone designator. If you do, Command Centre will make an assumption.
Item status
Most items have a status. Access zones, for example, can be in one of a few different modes, and have a zone count that rises and falls with cardholders arriving and leaving. Inputs and outputs can be on or off. Any item can be in an unknown state when the REST server is out of touch with its hardware. Et cetera.
The REST API can send an item's status to you in a string ready for human consumption in a UI, or as a list of flags more suitable for an integration. Neither of those fields are on an item's summary or details pages by default; to obtain them reliably you need to subscribe to updates.
Subscribing to status updates
The server does not always have an item's status: to prevent unnecessary work on the controller, network, and server, it will stop requesting updates when nothing is subscribed to them. It is very important that items stay in touch with their controllers, and controllers with each other, but it is normal for controllers to limit what they send back to the server.
The server will be up to date when one of the following happened recently:
- an operator had the item visible in the Configuration, Command Centre, or mobile clients,
- an application monitored the item via another API,
- a REST client used the item in a status subscription, or
- a REST client followed the item's
updateslink from its details page.
If it is not up to date and you request the status on an item's summary or details page, the server will report that the status is unknown.
To retrieve the status of one item you should GET the item's updates link from its details
page. To monitor the status of many items you should use the status subscription
API on the items controller (added in 8.30).
Whichever API you use, if the server does not have an item's status when you ask it will request an update from the item itself. For hardware items that may involve a conversation with another server, a hardware controller, and another piece of hardware on the end of a serial line. That all takes time, but the server answers you immediately, so the first response you receive may be 'unknown'. The status request continues in the background.
No matter what you receive, you should follow the next link in a loop until it gives you a
status you are looking for, or another status indicating why it cannot. Your original request
started a subscription, which every subsequent request refreshes, so while you stay in that
loop the server will have the item's current status for you and all other callers.
The first status you will receive after 'unknown' will probably be 'controller unknown', which means that the controller (or other hardware) has received the server's request but does not yet have an answer, because of that serial line. You need something better, so stay in the loop.
Staying current
When you use the updates link on the item's details page it will return immediately. When
you follow the next link in the page it returns, it will send back the status straight away if
it changed since your previous call, or block until it does change. If nothing happens within
a minute or so, it will return with no updates.
Whether you received an update or not, the body will contain a next link for you to follow
for the next update.
State changes are not queued: the API only keeps the last, so it only takes one call to get yourself up to date with the server. Remember that the server itself may not have been up to date, and your making the call will have started it on its own little journey of discovery, which will cause more updates.
The 'Site' tab of the sample application shows this in operation.
For a user interface
The statusText field describes the state of the item in a multi-line string taken from the
server's language pack. The natural state of a door, for example, is
Closed, locked, secure access.
A fence zone's status text could be
On - HV. Voltage: 8.2 kV. Seven day High Voltage min to max: 0.1kV to 9.8kV. Pre-arm check while Alarm Zone is arming.
For one-line display, the status field is the same thing with spaces instead of line
endings.
Neither of these is intended for integrations. By all means display them in a user interface (as Gallagher software does) but do not attempt to parse information out of them.
For an integration
The statusFlags field contains an array of string enumerations (flags) that describe the
item's condition in a reliable and machine-readable way.
Each item type has a different set of flags. Some overlap (doors, inputs, and outputs can all be closed, for example) but most flags apply to only one kind of item. The common exceptions are the flags which indicate why the server cannot return the item's actual status, covered next.
Abnormal status flags
These are the status flags that indicate the server does not have the current status of the item:
unsavedanddeletedare transient conditions you should not encounter in normal use. Either way, the item is in no condition to query.unconfiguredis very common while a site is being set up. It means the item does not have all the configuration it needs for normal operation. For example, an access zone is not configured until it has at least one door.remoteServerOfflineis only possible in a multi-server setup. It means that the item is remote (on a different server) and the server answering your REST query cannot reach it.processOfflinemeans the Controller service that is meant to be running on the Command Centre server, and handling all the communications with hardware controllers, is not.controllerOfflinemeans the software on the server is as it should be but the hardware controller (such as a C6000) is offline. That could mean it needs its certificate revalidated, or just that its power or networking is out.notPolledmeans the item is shunted: Command Centre is ignoring the item's communications at the request of an operator. You normally do it to stop spurious alarms.deviceNotRespondingmeans exactly that. It means the server is in contact with the hardware controller, but there is a problem between there and the item. Probably a cable fault, unlessencryptionKeysTamperedaccompanies it.
Those are fault conditions. If you see one of those, there is a configuration or hardware fault or a shunt preventing the server communicating with the item.
They are in priority order: if you receive one near the bottom of the list you can take heart in the knowledge that your item is suffering none of the preceding abnormalities.
There are two more flags that are common to most item types.
unknownmeans everything else is in order but you caught the server in a period when it simply had no need to stay in touch with the item. You will get this on a summary or details page, because they do not subscribe to updates for the item. The 'updates' link will not send you this status flag.controllerUnknownis a transient state, hopefully. The 'updates' call often returns it the first time if the server does not have the item's status already. It means a component (such as a hardware controller) between the server and your item is working on bringing in an update. This generally resolves quickly, so if you follow the 'next' link you will receive the latest status.
The server will not send any other flags with one of those eleven, apart from the possible
pairing of encryptionKeysTampered and deviceNotResponding, and some unusual combinations
noted later.
Do not go looking for all of the abnormal status flags above and assume the best if they are not there. Gallagher may add more fault conditions in future versions of the API.
Instead, read each item's section under Operations for what its flags will be when it is online. A paragraph called "flag rules" shows how you can tell if the item is in a correct state. For the impatient: doors, inputs, and outputs will be open or closed, fence zones will be on or off, and alarm and access zones will be in one of four zone states.
Authentication
API_keyapiKeyClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
API Key: Authorization in header
basichttpThis is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
Scheme: basic
Access Groups
These methods give you access to the access groups in Command Centre. You can search for groups, see their lineage, and list their cardholder members. In 8.40 or later you can see how they affect those members. In 9.30 and later you can create, modify, and delete them.
Your REST operator will need the 'View' or 'Edit Access Groups' operator privileges for the GETs, and 'Edit Access Groups' for the POST and PATCH.
These methods also provide the links you need to manage memberships. Those links are hrefs to cardholders, to which you would send PATCH requests containing your updates. Your REST operator will need 'Edit Cardholders' privilege on the cardholder for that.
Use cases
Finding members of an access group, and managing memberships.
GET /api.Follow the link at
features.accessGroups.accessGroups.href↪ (adding search terms, and settingtophigh to save pagination).Find your access group in the results.
The link at
cardholders↪ returns the cardholders who have direct membership of your group. This is only a subset of the cardholders who are affected by that group, as the groups that list this group as their parent inherit its effects recursively. If you are after every cardholder who has effective membership of your group, follow the group's href to get its detail page, which includes its child groups in an array calledchildren. In a depth-first traversal you would recurse down through the hrefs in that array before proceeding.Get the link at
cardholders(which is the same on the search results and the details page), look in that cardholder'saccessGroupsarray, and manage each membership as you require. Send a DELETE to a membership's href to remove it, or an HTTP PATCH to the cardholder href to update it.
Building the group hierarchy.
GET /api.- Follow the link at
features.accessGroups.accessGroups.href↪. If you follow the efficiency tips in the cardholder section you will addtopandsortparameters. - Record each group's href, name (if you intend to display the results), and
parent.href, if it is there. An access group may have no parents or one parent, never more. - Follow the link at
next.href, if there is one, and repeat.
You can now assemble the groups into a tree.
Licensing
Reading access groups and managing access group memberships is enabled by the RESTCardholders licence but creating, deleting, or modifying access group items requires the RESTConfiguration licence.
Search access groups
This returns access groups matching your search criteria.
The result will contain no more than 100 or 1000 groups depending on your version; you
should follow the next link, if it is present, to collect the next batch.
When you have loaded all the access groups there will be no next link.
If your result set is empty it means your operator does not have the privilege to view any access groups. Perhaps there are none in the divisions in which your operator has 'View access groups' or 'Edit access groups', or your operator has no privileges at all.
This request does not return the group's cardholders. That would make the results unwieldy. Instead, it provides a separate link.
Adding, deleting, or modifying access groups between calls to this API will not affect the pagination of its results if you sort by ID.
You can find the URL for this call in the features.accessGroups.accessGroups.href field of
/api. In the interest of forward compability, do not build it
yourself.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1querySets the maximum number of access groups to return per page. The default depends on your server version; you should set it appropriately for your application.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnamedescriptionparentdivisionchildrennotespersonalDataDefinitionscardholdersaccesssaltoAccessalarmZones...defaultsdefaultsquerySets the list of fields to return in the search results. The values you can list are the same as the field names in the details page. Using it you can return everything on the search page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
In v8.00 you will receive the href and internal ID even if you didn't ask for them. In 8.10 you will not. If you are going to send the fields parameter and need the href or ID, include them.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_groups", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_groups"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_groups'const response = await fetch('https://127.0.0.1:8904/api/access_groups', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_groups', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_groups')
data = response.json(){
"results": [
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"id": "352",
"href": "https://host.com:8904/api/access_groups/352",
"notes": "",
"cardholders": {
"href": "https://host.com:8904/api/access_groups/352/cardholders"
},
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://host.com:8904/api/access_groups?skip=61320"
}
}Create an access group
Creates a new access group, setting its basic fields, parent, PDFs, and some membership defaults.
Do not code this URL into your application. Take
it from the href field in the features.accessGroups.accessGroups.href field of GET /api.
The POST expects a document in the same format as the access group
detail. All fields except division are optional.
You will achieve better performance if you combine all you want to achieve into one POST, rather than creating the access group bare with a POST then refining it with PATCHes later.
When successful it returns a location header containing the address of the new access group.
Note that you can only create one access group per POST.
This call requires the RESTConfiguration licence and a server running 9.30 or later.
Body
This is the POST body you would use to create an access group. A division is mandatory.
These are the fields common to the POST and PATCH.
These are the fields common to the access group summary, details, POST, and PATCH.
namestringAll items have a name. Command Centre makes one up for you if you omit it when creating an item.
descriptionstringdivisionobjectMandatory when creating any access group. In this example, we want the access group in division 352.
parentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
notesstringparentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
membershipAutoRemoveExpiredbooleanIf true, memberships will be removed when their until time passes. If false,
they will remain on the cardholder, inactive.
membershipFromDefaultstring<date-time>When a cardholder is granted membership of this access group without specifying a 'from' time, this default will apply. If left blank, such a membership will not have a 'from' time. It will be effective immediately, provided its 'until' time is blank or in the future.
To clear it, send the blank string "".
membershipUntilDefaultstring<date-time>When a cardholder is granted membership of this access group without specifying an 'until' time, this default will apply. If blank, such a membership will not have an 'until' time. It will be effective until removed.
To clear it, send the blank string "".
This is where you give PDFs to an access group. All the members of the access group will be able to have values for the PDFs you list in this array.
Each element should contain the href of a PDF definition. You can take those from the PDFs
controller or other API routes, including the personalDataDefinitions block of another
access group.
The GET returns the names of the PDFs, but the POST will ignore them since these API calls are about access groups. If you need to change the name of a PDF, use that controller.
personalDataDefinitionsArray<object>Response
Success.
The body of the POST did not describe a valid access group.
If you see 'Invalid Access Group JSON object', the server could not parse the JSON in the body of your POST. Remember to quote all strings, especially those than contain @ symbols.
The operator does not have a privilege that allows creating access groups, or the server does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_groups", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_groups") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_groups' \
-H 'Content-Type: application/json' \
-d '{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}'const response = await fetch('https://127.0.0.1:8904/api/access_groups', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_groups', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": True,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
response = requests.post('https://127.0.0.1:8904/api/access_groups', json=payload)
data = response.json(){
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}Get details of an access group
In addition to the group's vitals and a link to the membership document in the access group search results, this call lists the group's child groups.
Note that you can obtain the same results by adding a fields query parameter to a
search.
You can find the URL for this call in the access group search results and in a cardholder's
accessGroups array. In the interest of forward compability, do not build it
yourself.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnamedescriptionparentdivisionchildrennotespersonalDataDefinitionsaccesssaltoAccessalarmZones...defaultsdefaultsquerySets the list of fields to return. The values you can list are the same as the field names in the detail results. Use it to return less data than normal. Separate values with commas.
Treat the string matches as case sensitive.
In v8.00 you will receive the href and internal ID even if you didn't ask for them. In 8.10 you will not. If you are going to send the fields parameter and need the href or ID, include them.
Response
Success.
That is not the URL of an access group, or the operator does not have a privilege that allows viewing it, such as 'Modify Access Control' or 'View Access Groups'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_groups/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_groups/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_groups/{id}'const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_groups/{id}')
data = response.json(){
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"id": "352",
"href": "https://host.com:8904/api/access_groups/352",
"notes": "",
"cardholders": {
"href": "https://host.com:8904/api/access_groups/352/cardholders"
},
"serverDisplayName": "ruatoria.satellite.int",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
],
"children": [
{
"href": "https://host.com:8904/api/access_groups/5122",
"name": "R&D super-special projects"
},
{
"href": "https://host.com:8904/api/access_groups/3420",
"name": "R&D social committee"
}
],
"visitor": false,
"escortVisitors": false,
"lockUnlockAccessZones": false,
"enterDuringLockdown": false,
"firstCardUnlock": false,
"overrideAperioPrivacy": false,
"aperioOfflineAccess": false,
"disarmAlarmZones": false,
"armAlarmZones": false,
"hvLfFenceZones": false,
"viewAlarms": false,
"shunt": false,
"lockOutFenceZones": false,
"cancelFenceZoneLockout": false,
"ackAll": false,
"ackBelowHigh": false,
"selectAlarmZone": false,
"armWhileAlarm": false,
"armWhileActiveAlarm": false,
"isolateAlarmZones": false,
"access": [
{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/333",
"name": "Twilight zone"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/5",
"name": "Default Cardholder Access Granted"
}
},
{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412",
"name": "Server room"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/557",
"name": "8am-5pm weekdays"
}
}
],
"saltoAccess": [
{
"saltoItemType": {
"value": "saltoAccessZone"
},
"saltoItem": {
"href": "https://host.com:8904/api/items/570",
"name": "Salto BLE CV19"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/5",
"name": "Default Cardholder Access Granted"
}
},
{
"saltoItemType": {
"value": "saltoDoor"
},
"saltoItem": {
"href": "https://host.com:8904/api/items/579",
"name": "Salto CU5000"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/557",
"name": "8am-5pm weekdays"
}
}
],
"alarmZones": [
{
"alarmZone": {
"href": "https://host.com:8904/api/alarm_zones/328",
"name": "Roswell building 2 lobby alarms"
}
},
{
"alarmZone": {
"href": "https://host.com:8904/api/alarm_zones/10138",
"name": "Roswell building 3 lobby alarms"
}
}
]
}Remove an access group
This call removes an access group from Command Centre.
You can find the URL for this call in the access group
search results and in a cardholder's accessGroups
array. In the interest of forward compability, do not build it
yourself.
This call requires the RESTConfiguration licence and a server running 9.30 or later.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Success.
Deleting the access group failed. This happens when it still has members.
The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.
That is not the URL of an access group or the operator is not privileged to view it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/access_groups/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/access_groups/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/access_groups/{id}'const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/access_groups/{id}')
data = response.json()Update an access group
This is the call you use to modify fields on an access group.
The PATCH expects a document in the same format as the the access group detail but with fewer fields. Note that you cannot change everything on an access group that the API shows you, such as its membership, access, and permissions. You can change its basic fields, PDFs, and membership defaults.
You can find the URL for this call in the access group
search results and in a cardholder's accessGroups
array. In the interest of forward compability, do not build it
yourself.
This call requires the RESTConfiguration licence and a server running 9.30 or later.
Body
The body you would send to an access group PATCH has the same schema as an access group
POST apart from the personalDataDefinitions
block.
These are the fields common to the POST and PATCH.
These are the fields common to the access group summary, details, POST, and PATCH.
namestringAll items have a name. Command Centre makes one up for you if you omit it when creating an item.
descriptionstringdivisionobjectMandatory when creating any access group. In this example, we want the access group in division 352.
parentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
notesstringparentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
membershipAutoRemoveExpiredbooleanIf true, memberships will be removed when their until time passes. If false,
they will remain on the cardholder, inactive.
membershipFromDefaultstring<date-time>When a cardholder is granted membership of this access group without specifying a 'from' time, this default will apply. If left blank, such a membership will not have a 'from' time. It will be effective immediately, provided its 'until' time is blank or in the future.
To clear it, send the blank string "".
membershipUntilDefaultstring<date-time>When a cardholder is granted membership of this access group without specifying an 'until' time, this default will apply. If blank, such a membership will not have an 'until' time. It will be effective until removed.
To clear it, send the blank string "".
personalDataDefinitionsobjectThis object can contain two arrays named add and remove. Every element
you put in those arrays should contain a string called href giving the href
of a PDF that you want to add to or remove from the access group.
In this example we are adding PDF 5516 and removing 9370.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid access group. See the body of the response for help on what went wrong. It may be that you tried to use the name of another access group: no two items of the same type can have the same name. Or you may have tried to set the division to one that is not visible to you, or you may have attempted to create a loop in the access group hierarchy.
The operator has a privilege that allows viewing the item but not modifying it, or you tried to set the division to one you cannot configure, or the server is missing the necessary licence.
You need the 'Edit Access Groups' privilege on the item you are changing, which means you need on it on the item's current division. You also need it on the new division, if you are changing that.
This is also the response when the server does not have the 'RESTConfiguration' licence.
That is not the URL of an access group or your operator does not have the privilege to view it. This probably means you have built the URL yourself instead of taking it from the results of a GET.
The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/access_groups/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/access_groups/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/access_groups/{id}' \
-H 'Content-Type: application/json' \
-d '{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}'const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": True,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}
response = requests.patch('https://127.0.0.1:8904/api/access_groups/{id}', json=payload)
data = response.json(){
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z",
"personalDataDefinitions": {
"add": [
{
"href": "https://host.com:8904/api/personal_data_fields/5516"
}
],
"remove": [
{
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}
}Get membership of an access group
This lists all cardholders who are direct members of a particular group. It does
not paginate the results, so there is no next link. That can make for a large
document, so it omits the group's child groups and their cardholder members. It
is not recursive, in other words.
There may be more than one entry per cardholder, because any one cardholder can have many memberships to a group, each with different from and until date-times.
If your operator does not have the privilege to view a cardholder item you will receive its name but not its href (since following it would 404).
You can find the URL in the cardholders block of an access group's search results or
detail pages. In the interest of forward compability, do not build it
yourself.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
The ID is invalid, or it is valid but you do not have privileges for the access group. Check the body of the result for a description of the problem.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_groups/{id}/cardholders", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_groups/{id}/cardholders"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_groups/{id}/cardholders'const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}/cardholders', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_groups/{id}/cardholders', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_groups/{id}/cardholders')
data = response.json(){
"cardholders": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/D714D8A89",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://host.com:8904/api/cardholders/325"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z"
},
{
"href": "https://host.com:8904/api/cardholders/329/access_groups/18DE901A1",
"cardholder": {
"name": "Miles Messervy"
},
"from": "2016-11-18T00:00:00.000Z"
}
]
}Access Zones
These methods give you read access to Access Zones in the Command Centre database, and let you change their modes, lock them down, and send other overrides.
The first use case below introduces the main entry point. It is a paginated search interface that gives any number of access zones, each containing the fields you ask for in the query.
Overrides
Note:
- End-times on overrides are not accurate to the second. Internally, Command Centre converts the end time to a duration, so you may find that submitting end times in the very near future does not have the exact effect you expect.
- An override without an end time lasts until the next mode change or 'cancel untimed overrides' entry in the access zone's schedule.
- The end time you set for an override cannot be in the past or more than 24 hours into the future.
Access Zone status flags
If the access zone is online, its statusFlags field may contain one or more of these flags:
saltoOutputOnlymeans the the access zone would be considered unconfigured because it is missing doors, and therefore offline, except that it has a Salto output number assigned to it. That is a normal operational configuration.mobileZonealso means the access zone would be considered unconfigured (missing doors) and therefore offline, except that a mobile reader is able to badge cardholders into it, making it a perfectly useful access zone. It will also have asecureflag.lockedDownmeans only cardholders with the privilege to enter locked-down zones shall pass. The zone will also be 'secure'. When the lockdown override ends it will return to its previous access mode.usePinmeans when you use a card at a reader to unlock the door or at a terminal to control an alarm zone, you will also need your PIN.zoneCountTooHighmeans the zone count is above its maximum.zoneCountTooLowmeans the zone count is below its minimum.zoneCountInGracemeans the zone count recently became too low or high.
The following four give the zone's access state. An online access zone will always return one of them.
securemeans the doors are locked.dualAuthmeans the doors are locked and - depending on configuration - cardholders will need a second credential to open them.codeOrCardmeans you can unlock a door either with a card, the zone's access code, or (if suitably configured on the reader) a personal user code. You will not see this flag if the zone is locked down.freemeans the zone's doors are unlocked. You will not see this when the zone is locked down.
Access Zone flag rules
If and only if the zone online and not locked down, there will be exactly one of 'secure', 'dualAuth', 'codeOrCard', or 'free'.
If and only if the zone online and locked down, there will be exactly one of 'secure' or 'dualAuth'.
Because 'saltoOutputOnly' and 'mobileZone' are variations of the unconfigured offline condition, 'saltoOutputOnly' will be alone and only 'secure' will accompany 'mobileZone'.
If a zone has both a Salto output number and mobile access, only 'saltoOutputOnly' will appear.
If there is 'zoneCountInGrace' there will always be exactly one of 'zoneCountTooHigh' or 'zoneCountTooLow' (never both).
Using the first three rules above, your test for an access zone being in error is 'secure', 'dualAuth', 'codeOrCard', 'free', 'saltoOutputOnly', and 'mobileZone' all missing.
Use cases
Searching for access zones by name
GET /api.- Follow the link at
features.accessZones.accessZones.href↪, appending a search term such asname=substringto filter the access zones, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink until there isn't one.
Changing an access zone's access mode
- Find the href for the access zone using the process above.
- GET it.
- Find the API URL you require in the
commandsstructure of the results, such asfreeorsecurePin, from the detail. Use theuntilvariants if you are specifying an end time. - POST to that URL. Some require a JSON object (resetting the zone count, and all
those with
untilin their command block keys). The others expect an empty body.
Finding an access zone's status
- Find the href for the access zone using the process above, and GET it.
- Take the
updates↪ href from that page. For a version 8.00 server append the query parameter separator (?or&) thenfields=defaults,zoneCountif you need the zone count as well as the default status fields. The zone count is included by default in 8.10 and later. - GET it.
- Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search access zones
This returns a summary of the access zones matching your search criteria.
The result will contain no more than 100 or 1000 access zones (depending on your version),
or as many as you asked for more in your request; you should follow the next link, if it
is present, to collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any access zones, such as 'View Site', 'Edit Site', or 'Override'. Perhaps there are no access zones in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take
it from the 'href' field in the features.accessZones.accessZones section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerdoorszoneCountstatusFlagsstatusTextstatusnotesupdatesdefaultsdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_zones", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_zones"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_zones'const response = await fetch('https://127.0.0.1:8904/api/access_zones', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_zones')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/access_zones/3280",
"id": "3280",
"name": "Roswell building 2 lobby"
}
],
"next": {
"href": "https://localhost:8904/api/access_zones?skip=1000"
}
}Search targetable access zones
This returns a list of the access zones to which your operator is allowed to move cardholders, and a special zone you can use as a target to remove a cardholder from all access zones.
Like all other paginated queries in this API, the result will contain no more than 100
or 1000 access zones (depending on your version), or as many as you asked for more in your
request; you should follow the next link, if it is present, to collect the next batch.
When you have loaded them all there will be no next link.
If your result set is empty it means there are no access zones in the divisions in which your operator has the privilege to move cardholders ('Manage Cardholder Location'), or your operator does not have the privilege at all.
Do not code this URL into your application. Take it from the 'href' field in the
features.cardholders.updateLocationAccessZones section of /api.
Added in 8.20.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerdoorszoneCountstatusFlagsstatusTextstatusnotesupdatesdefaultsdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_zones/update_cardholder_location", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_zones/update_cardholder_location"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_zones/update_cardholder_location'const response = await fetch('https://127.0.0.1:8904/api/access_zones/update_cardholder_location', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/update_cardholder_location', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_zones/update_cardholder_location')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/access_zones/3280",
"id": "3280",
"name": "Roswell building 2 lobby"
}
],
"outsideOfSystem": {
"href": "https://localhost:8904/api/access_zones/0"
},
"next": {
"href": "https://localhost:8904/api/access_zones?skip=1000"
}
}Get details of an access zone
This returns the detail of one access zone.
Follow the 'href' field in an access zone summary to get here rather than building it yourself.
Parameters
idstringrequiredpathfieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerdoorszoneCountstatusFlagsstatusTextstatusnotesupdatesqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an access zone, or the operator does not have a privilege on the zone's division that allows viewing access zones, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_zones/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_zones/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_zones/{id}'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_zones/{id}')
data = response.json(){
"href": "https://localhost:8904/api/access_zones/3280",
"id": "3280",
"name": "Roswell building 2 lobby",
"description": "Receives all visitors.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"doors": [
{
"name": "Front door",
"href": "https://localhost:8904/api/doors/332"
},
{
"name": "West stairwell lobby door",
"href": "https://localhost:8904/api/doors/745"
}
],
"zoneCount": 365,
"notes": "Multi-line text...",
"shortName": "Short text",
"updates": {
"href": "https://localhost:8904/api/access_zones/3280/updates/0_0_0"
},
"statusFlags": [
"secure"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"free": {
"href": "https://localhost:8904/api/access_zones/333/free"
},
"freeUntil": {
"href": "https://localhost:8904/api/access_zones/333/free"
},
"freePin": {
"href": "https://localhost:8904/api/access_zones/333/free_pin"
},
"freePinUntil": {
"href": "https://localhost:8904/api/access_zones/333/free_pin"
},
"secure": {
"href": "https://localhost:8904/api/access_zones/333/secure"
},
"secureUntil": {
"href": "https://localhost:8904/api/access_zones/333/secure"
},
"securePin": {
"href": "https://localhost:8904/api/access_zones/333/secure_pin"
},
"securePinUntil": {
"href": "https://localhost:8904/api/access_zones/333/secure_pin"
},
"codeOnly": {
"href": "https://localhost:8904/api/access_zones/333/code_only"
},
"codeOnlyUntil": {
"href": "https://localhost:8904/api/access_zones/333/code_only"
},
"codeOnlyPin": {
"href": "https://localhost:8904/api/access_zones/333/code_only_pin"
},
"codeOnlyPinUntil": {
"href": "https://localhost:8904/api/access_zones/333/code_only_pin"
},
"dualAuth": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth"
},
"dualAuthUntil": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth"
},
"dualAuthPin": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth_pin"
},
"dualAuthPinUntil": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth_pin"
},
"forgiveAntiPassback": {
"href": "https://localhost:8904/api/access_zones/333/forgive_anti_passback"
},
"setZoneCount": {
"href": "https://localhost:8904/api/access_zones/333/set_zone_count"
},
"lockDown": {
"href": "https://localhost:8904/api/access_zones/333/lock_down"
},
"cancelLockDown": {
"href": "https://localhost:8904/api/access_zones/333/cancel_lock_down"
},
"cancel": {
"href": "https://localhost:8904/api/access_zones/333/cancel"
}
}
}Set access zone free
Sends an override to an access zone to change its mode to 'free - no PIN', meaning the doors will be free and you will not need a PIN to perform an override on a terminal.
Take this URL from the commands block of an access zone. In there is a block called
free, containing a field href. That is the URL of this method.
The URL from commands.freePin.href (as opposed to "free" without the "Pin") will send an override to an
access zone to change its mode to 'free - PIN', meaning you will need a PIN on a terminal.
If you do not give the override an end time in the body it will remain in place until the next scheduled change.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/free", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/free") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/free' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/free', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/free', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/free', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Set access zone secure
Sends an override to an access zone to change its mode to 'secure - no PIN', meaning you will need a card, but not a PIN, to open its doors or perform overrides on terminals.
Take this URL from the commands block of an access zone. In there is a block called
secure, containing a field href. That is the URL of this method.
The URL from commands.securePin.href (as opposed to without the "Pin") will send an override to an
access zone to change its mode to 'secure - PIN', meaning you will need a PIN on a terminal.
If you do not give the override an end time in the body, it will remain in place until the next scheduled change.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/secure", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/secure") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/secure' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/secure', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/secure', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/secure', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Set access zone to code or card
Sends an override to an access zone to change its mode to 'Code or Card - No PIN', meaning you can use your user code or the zone's 'code-only code' to open its doors, depending on the reader's configuration. You will not need a PIN on a terminal.
Take this URL from the commands block of an access zone. In there is a block called
codeOnly, containing a field href. That is the URL of this method.
The URL from commands.codeOnlyPin.href (as opposed to without the "Pin") will send an override to an
access zone to change its mode to 'Code or Card - PIN', meaning you will need a PIN on a terminal.
If you do not give the override an end time in the body it will remain in place until the next scheduled change.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/code_only", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/code_only") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/code_only' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/code_only', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/code_only', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/code_only', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Set access zone to dual auth
Sends an override to an access zone to change its mode to 'dual auth - no PIN', meaning you will need two badges to open its doors (either two different cardholders or two credentials for the same cardholder, depending on the access zone's configuration), and they will not need a PIN. Terminal functions will require a card but no PIN.
Take this URL from the commands block of an access zone. In there is a block called
dualAuth, containing a field href. That is the URL of this method.
The URL from commands.dualAuthPin.href (as opposed to without the "Pin") will send an override to an
access zone to change its mode to 'dual auth - PIN', meaning you will need a PIN on a terminal.
If you do not give the override an end time in the body it will remain in place until the next scheduled change.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/dual_auth", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/dual_auth") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/dual_auth' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/dual_auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/dual_auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/dual_auth', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Forgive antipassback on a zone
Sends an override to an access zone to forgive anti-passback for all cardholders in the zone.
Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback')
data = response.json()Lock a zone down
Locks down an access zone. In this mode, cardholders will need the 'Entry allowed during lockdown' privilege to enter the zone, in addition to normal access.
Take this URL from the commands block of an access zone. In there is a block called
lockDown, containing a field href. That is the URL of this method.
It takes no parameters. The lockdown will remain in place until cancelled, or the access zone receives an override to another mode.
Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/lock_down", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/lock_down"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/lock_down'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/lock_down', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/lock_down', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/lock_down')
data = response.json()Cancel a zone lockdown
Cancels a lockdown, returning it to its scheduled state. It will not cancel any other kind of override.
Take this URL from the commands block of an access zone. In there is a block called
cancelLockDown, containing a field href. That is the URL of this method.
Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down')
data = response.json()Set a zone count
Sets the count of cardholders inside a zone.
Body
The new cardholder count for the zone.
zoneCountintegerrequiredPut this in the body of access zone override POSTs to set the count of cardholders in the access zone.
Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"zoneCount": 100
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"zoneCount": 100
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count' \
-H 'Content-Type: application/json' \
-d '{
"zoneCount": 100
}'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"zoneCount": 100
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"zoneCount": 100
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"zoneCount": 100
}
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count', json=payload)
data = response.json(){
"zoneCount": 100
}Cancel mode override
Cancels an override, returning the access zone to its scheduled state.
Take this URL from the commands block of an access zone. In there is a block called
cancel, containing a field href. That is the URL of this method.
This command will achieve nothing if the alarm zone is not controlled by a schedule, because without a schedule the alarm zone does not have the concept of a 'normal' state.
It will not cancel a lockdown. For that you need cancel_lock_down.
Parameters
idstringrequiredpathThe ID of the access zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding access zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/access_zones/{id}/cancel", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/access_zones/{id}/cancel"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/access_zones/{id}/cancel'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/cancel', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/cancel', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/access_zones/{id}/cancel')
data = response.json()Monitor an access zone
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in an access zone summary or details pages to get here rather than building it yourself.
Parameters
idstringrequiredpathThe ID of the access zone.
fieldsstringstatusstatusTextstatusFlagszoneCountqueryThis instructs the server to return these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.
Response
Success. The introduction describes the three status fields and
the access zone detail describes zoneCount.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an access zone, or the operator does not have a privilege on the zone's division that allows viewing access zones, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/access_zones/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/access_zones/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/access_zones/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/access_zones/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/access_zones/{id}/updates')
data = response.json(){
"updates": {
"status": "Secure.",
"statusText": "Secure.",
"statusFlags": [
"secure"
],
"zoneCount": 100
},
"next": {
"href": "https://localhost:8904/api/access_zones/3280/updates/9_1"
}
}Alarms
Use these methods to download, monitor, and manage Command Centre alarms.
Alarm states
Each type of alarm can be stateless or stateful. Stateless alarm types are spikes, such as an operator getting their password wrong or a cardholder being denied at a door. Stateful alarms have a triggering event and a restoring event, such as a door being forced open then closed, or a service going offline then reappearing later.
An operator cannot process (dismiss) an active alarm without the 'Force process' privilege.
The API presents the difference in a field called active. It will always be false for
stateless alarms, and it will be true for stateful alarms before their restoring event occurs.
Alarm use cases
Downloading and managing unprocessed alarms
GET /api- Follow the link at
features.alarms.alarms↪. You will receive up to 100 alarms, each containing links to its management functions. - If step 2 returned results and there is a link at
next.href, follow it and repeat.
Staying up to date
After getting all the current alarms using the process above, which is following next.href in
a loop until there are no more alarms to get, follow the link at updates.href. It will block
until there is a new alarm or a change to an existing alarm. After handling that update, follow
the link it contains at next.href and keep following it in a loop to stay up to date.
Licensing
- The GETs that collect alarms and events require the RESTEvents licence.
Get current alarms
This returns the current list of unprocessed alarms. The result will contain no more
than 100 alarms; you should follow the next link, if it is present, to collect
more.
You can tell when you have loaded all the current alarms because there will not be
a next link. Instead, there will be an updates link which takes you to a different
endpoint that long-polls for live updates to alarms.
Unlike the corresponding method that retrieves events, this call does not take query parameters to filter its results. You can limit the fields it returns, but it will always return every alarm that your operator can view. If you wish to restrict it to alarms in certain divisions, give your operator permission to view alarms in only those divisions.
The alarm summary only contains unprocessed alarms. You can access
a processed alarm by finding its corresponding event in the event summary
and following its alarm.href link to the alarm details.
Do not code this URL into your application. Take it from alarms.alarms.href in the
results of GET /api.
Parameters
fieldsstringhrefidtimemessagesourcetypeeventTypeprioritystateactivedivisionnotePresetsviewcommentacknowledgeWithCommentacknowledgeprocessWithCommentprocessdetailshistoryinstructioncardholdereventquerySets the fields you want in your results. Separate fields with commas.
New to 8.40.
Response
Success
The site does not have a RESTEvents licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/alarms", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/alarms"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/alarms'const response = await fetch('https://127.0.0.1:8904/api/alarms', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/alarms')
data = response.json(){
"alarms": [
{
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}
],
"next": {
"href": "https://localhost:8904/api/alarms?start=92143&pos=61320"
},
"updates": {
"href": "https://localhost:8904/api/alarms/updates?id=92143.1"
}
}Get changes to alarms (or wait)
This is a long poll for live updates to alarms. If alarms occurred or changed since the
previous call, no matter how long ago that was, it will return them immediately. If there
are no updates pending, it will wait until one arrives. If none arrive before a timeout
passes (about 30 seconds) it will return an empty updates array and a fresh next link.
Whether or not the response contained updates the client should follow the next link to
wait for the next updates.
If your server receives a lot of alarm updates, wait some time between calls to reduce chatter. You will not miss any alarms: each response contains all the alarms that happened since the previous call, even if an operator processed the alarms in question.
You will receive each alarm only once in a result set, in its current state. For example if an alarm is raised, restored, acknowledged, then processed between two of your API calls you will receive it as 'processed' in your next call. If, however, you make API calls between each of the alarm's state changes you will receive it four times: active, inactive and unacknowledged, acknowledged, then finally processed.
Do not wait if you receive 100 updates or more. 100 is the maximum number of updates current versions of CC will return in one batch, so if that happens you have fallen behind: there are more updates waiting and you can catch up by asking for them immediately.
Command Centre does not tell you which alarms were added, removed, or modified. It is up to you to match the incoming alarms against your own internal alarm list and determine the differences.
Do not code this URL into your application. Take it from alarms.updates.href in the
results of GET /api, or from updates in the results of GET /api/alarms, or from next
in the results of this call. Do not attempt to interpret or set the id query parameter in
the URL, as it tracks your progress through the alarm history.
Assuming you got the URL for this call from the response from another call, and your operator therefore has the privileges required to view alarms, this should always complete successfully.
Parameters
fieldsstringhrefidtimemessagesourcetypeeventTypeprioritystateactivedivisionnotePresetsviewcommentacknowledgeWithCommentacknowledgeprocessWithCommentprocessdetailshistoryinstructioncardholdereventquerySets the fields you want in your results. Separate fields with commas.
New to 8.40.
Response
Success
The site does not have a RESTEvents licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/alarms/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/alarms/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/alarms/updates'const response = await fetch('https://127.0.0.1:8904/api/alarms/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/alarms/updates')
data = response.json(){
"updates": [
{
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}
],
"next": {
"href": "https://localhost:8904/api/alarms/updates?id=10135"
}
}Get details of an alarm
Full details for an alarm. Follow the href in the alarm summary to get here.
Parameters
idstringrequiredpathAn internal identifier. Do not insert this into your request yourself. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.
fieldsstringhrefidtimemessagesourcetypeeventTypeprioritystateactivedivisionnotePresetsviewcommentacknowledgeWithCommentacknowledgeprocessWithCommentprocessdetailshistoryinstructioncardholdereventquerySets the fields you want in your results. Separate fields with commas.
New to 8.40.
Response
Success
That is not the href of an alarm or you do not have privileges for it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/alarms/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/alarms/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/alarms/{id}'const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/alarms/{id}')
data = response.json(){
"details": "Forced door",
"history": [
{
"time": "2016-02-18T19:21:52Z",
"action": "viewed",
"comment": "Operator viewed alarm properties",
"operator": {
"name": "System Operator"
}
}
],
"instruction": {
"href": "https://localhost:8904/api/alarms/92210/instructions",
"name": "Forced door instruction"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Smith, Jane",
"firstName": "Jane",
"lastName": "Smith-Jones"
},
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}Mark alarm viewed
Mark the alarm as viewed. Follow the view link in the alarm summary to get here.
You can do this for an alarm in any state, even processed, provided you have its URL and sufficient privilege.
Body
Optional comment.
commentstringOptional for some methods that update alarms. Contains a comment placed by the operator.
Parameters
idstringrequiredpathAn internal identifier. Do not insert this into your request yourself. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.
Response
Success
That is not the href of an alarm or you do not have privileges for it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"comment": "Alarm was adequately explained."
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarms/{id}/view", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"comment": "Alarm was adequately explained."
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarms/{id}/view") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarms/{id}/view' \
-H 'Content-Type: application/json' \
-d '{
"comment": "Alarm was adequately explained."
}'const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/view', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/view', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"comment": "Alarm was adequately explained."
}
response = requests.post('https://127.0.0.1:8904/api/alarms/{id}/view', json=payload)
data = response.json(){
"comment": "Alarm was adequately explained."
}Add a comment to an alarm
Follow the comment link in the alarm summary to get here.
You can comment on an alarm in any state, even processed, provided you have its URL and sufficient privilege.
Body
Optional comment.
commentstringOptional for some methods that update alarms. Contains a comment placed by the operator.
Parameters
idstringrequiredpathAn internal identifier. Do not insert this into your request yourself. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.
Response
Success
That is not the href of an alarm or you do not have privileges for it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"comment": "Alarm was adequately explained."
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarms/{id}/comment", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"comment": "Alarm was adequately explained."
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarms/{id}/comment") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarms/{id}/comment' \
-H 'Content-Type: application/json' \
-d '{
"comment": "Alarm was adequately explained."
}'const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/comment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/comment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"comment": "Alarm was adequately explained."
}
response = requests.post('https://127.0.0.1:8904/api/alarms/{id}/comment', json=payload)
data = response.json(){
"comment": "Alarm was adequately explained."
}Mark alarm acknowledged
Follow the acknowledgeWithComment or the acknowledge link in the alarm summary to get
here.
You can acknowledge an alarm that is in any state provided you have its URL and sufficient privilege. Doing so will add an entry to the alarm's history, but will have no other effect if it is already acknowledged or processed.
Body
Optional comment.
commentstringOptional for some methods that update alarms. Contains a comment placed by the operator.
Parameters
idstringrequiredpathAn internal identifier. Do not insert this into your request yourself. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.
Response
Success
That is not the href of an alarm or you do not have privileges for it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"comment": "Alarm was adequately explained."
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarms/{id}/acknowledge", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"comment": "Alarm was adequately explained."
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarms/{id}/acknowledge") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarms/{id}/acknowledge' \
-H 'Content-Type: application/json' \
-d '{
"comment": "Alarm was adequately explained."
}'const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/acknowledge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/acknowledge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"comment": "Alarm was adequately explained."
}
response = requests.post('https://127.0.0.1:8904/api/alarms/{id}/acknowledge', json=payload)
data = response.json(){
"comment": "Alarm was adequately explained."
}Mark alarm processed
Follow the processWithComment, forceProcess, or process link in the alarm to get here.
You can process an alarm that is in any state provided you have its URL and sufficient privilege. Doing so will add an entry to the alarm's history, but will have no other effect if it is already acknowledged or processed. If the alarm is active -- waiting for another event to restore it (such as a 'door open too long' waiting for the door to close) -- it will perform a force process.
Body
Optional comment.
commentstringOptional for some methods that update alarms. Contains a comment placed by the operator.
Parameters
idstringrequiredpathAn internal identifier. Do not insert this into your request yourself. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.
Response
Success
That is not the href of an alarm or you do not have privileges for it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"comment": "Alarm was adequately explained."
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarms/{id}/process", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"comment": "Alarm was adequately explained."
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarms/{id}/process") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarms/{id}/process' \
-H 'Content-Type: application/json' \
-d '{
"comment": "Alarm was adequately explained."
}'const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarms/{id}/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"comment": "Alarm was adequately explained."
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"comment": "Alarm was adequately explained."
}
response = requests.post('https://127.0.0.1:8904/api/alarms/{id}/process', json=payload)
data = response.json(){
"comment": "Alarm was adequately explained."
}Alarm Zones
These methods give you read access to Alarm Zones in the Command Centre database, and let you change their states: armed, disarmed, two user-defined states, and (if at least one Fence Zone is directing its events to this Alarm Zone) 'Armed - High Voltage' or 'Armed - Low Feel'.
Alarm zones may run by a schedule, but it is optional. Alarm zones that do not have a schedule change state only through manual overrides, meaning they do not have a natural state. That in turn means that you cannot override them for a duration -- they have no state to return to.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of alarm zones, each containing the fields you ask for in the query.
Overrides
Note:
- End-times on overrides are not accurate to the second. Internally, Command Centre converts the end time to a duration, so you may find that submitting end times in the very near future does not have the exact effect you expect.
- Submitting an override without an end time makes it take effect until the next state change or 'cancel untimed overrides' entry in the alarm zone's schedule.
- Overrides you submit via REST are not subject to the "manual unset" timeouts you can set in the Configuration Client. Those only affect readers, terminals, and pushbuttons.
- The end time you set for an override cannot be in the past or more than 24 hours into the future.
Alarm Zone status flags
If the alarm zone is online, its statusFlags field will contain one or more of these flags:
armedmeans the alarm zone is armed. It will be called that even if you have changed the terminology in the Server Properties.disarmedas above.user1will be called that even if you have given it a different name in Server Properties.user2as above.exitDelaymeans the zone is temporarily ignoring input triggers. One of the previous four flags shows you the state the zone is about to change away from.entryDelaymeans an input has triggered the alarm zone but Command Centre is giving a cardholder the opportunity to disarm the zone.lowFeelmeans the alarm zone has a fence zone attached, and that it is in 'low feel' mode (meaning it is delivering lower voltage than you would use for a strong deterrant).highVoltagemeans that the alarm zone has a fence zone attached, and that it is 'high voltage' mode (meaning it is delivering a strong deterrant pulse).
Alarm Zone flag rules
- If and only if the zone is online, there will exactly one of 'armed', 'disarmed', 'user1', or 'user2'. That is your test for whether an alarm zone is in error.
- 'exitDelay' and 'entryDelay' cannot appear together.
- If and only if the zone is online and has a fence zone, there will be exactly one of 'lowFeel' or 'highVoltage'.
Use cases
Searching for alarm zones by name
GET /api.- Follow the link at
features.alarmZones.alarmZones.href↪, appending a search term such asname=substringto filter the access zones, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink until there isn't one.
Changing an alarm zone's state
- Find the href for the alarm zone using the process above.
- GET it.
- Find the API URL you require in the
commandsstructure of the results, such asarm↪ ordisarm↪, using the detail. Use the calls withUntilon the ends of their names if you are specifying an end time. - POST to that URL. All those with
Untilin their names require a JSON object in the body giving the time at which the override should end; the others do not.
Finding an alarm zone's status
- Find the href for the alarm zone using the process above, and GET it.
- Follow the
updates↪ href from that page. - Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search alarm zones
This returns a summary of the alarm zones matching your search criteria.
The result will contain no more than 100 or 1000 alarm zones (depending on your version), or
as many as you asked for more in your request; you should follow the next link, if it is
present, to collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any alarm zones, such as 'View Site', 'Edit Site', or 'Override'. Perhaps there are no alarm zones in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.alarmZones.alarmZones section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/alarm_zones", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/alarm_zones"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/alarm_zones'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/alarm_zones')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/alarm_zones/328",
"id": "328",
"name": "Roswell building 2 lobby alarms"
}
],
"next": {
"href": "https://localhost:8904/api/alarm_zones?skip=1000"
}
}Get details of an alarm zone
This returns the detail of one alarm zone.
Follow the 'href' field in an alarm zone summary to get here rather than building it yourself.
Parameters
idstringrequiredpathThe ID of the alarm zone.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusnotesupdatesqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an alarm zone, or the operator does not have a privilege on the zone's division that allows viewing alarm zones, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/alarm_zones/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/alarm_zones/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/alarm_zones/{id}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/alarm_zones/{id}')
data = response.json(){
"href": "https://localhost:8904/api/alarm_zones/328",
"id": "328",
"name": "Roswell building 2 lobby alarms",
"description": "Lobby, cafeteria, inbound artefacts.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "R2 lobby",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/alarm_zones/328/updates/0_0_0"
},
"statusFlags": [
"armed"
],
"connectedController": {
"name": "Third floor C6000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"arm": {
"href": "https://localhost:8904/api/alarm_zones/328/arm",
"name": "Armed"
},
"armUntil": {
"href": "https://localhost:8904/api/alarm_zones/328/arm",
"name": "Armed"
},
"disarm": {
"href": "https://localhost:8904/api/alarm_zones/328/disarm",
"name": "Disarmed"
},
"disarmUntil": {
"href": "https://localhost:8904/api/alarm_zones/328/disarm",
"name": "Disarmed"
},
"user1": {
"href": "https://localhost:8904/api/alarm_zones/328/user1",
"name": "User1"
},
"user1Until": {
"href": "https://localhost:8904/api/alarm_zones/328/user1",
"name": "User1"
},
"user2": {
"href": "https://localhost:8904/api/alarm_zones/328/user2",
"name": "User2"
},
"user2Until": {
"href": "https://localhost:8904/api/alarm_zones/328/user2",
"name": "User2"
},
"cancel": {
"href": "https://localhost:8904/api/alarm_zones/328/cancel"
}
}
}Arm an alarm zone
Sends an override to an alarm zone to arm it.
If you send an end time in the body, and the alarm zone has a schedule to consult to find the state that it should return to, the override will only stay in effect until then.
If you do not, the override will remain in place until the next scheduled or manual change.
Follow one of the 'commands.arm' or 'commands.armUntil' fields in an alarm zone to get here rather than building it yourself.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/arm", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/arm") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/arm' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/arm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/arm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/arm', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Disarm an alarm zone
Sends an override to an alarm zone to disarm it.
If you send an end time in the body, and the alarm zone has a schedule to consult to find the state that it should return to, the override will only stay in effect until then.
If you do not, the override will remain in place until the next scheduled or manual change.
Follow one of the 'commands.disarm' or 'commands.disarmUntil' fields in an alarm zone to get here rather than building it yourself.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/disarm", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/disarm") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/disarm' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/disarm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/disarm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/disarm', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Change an alarm zone to user1
Sends an override to an alarm zone to set its state to 'user1', one of the custom states.
If you send an end time in the body, and the alarm zone has a schedule to consult to find the state that it should return to, the override will only stay in effect until then.
If you do not, the override will remain in place until the next scheduled or manual change.
Follow one of the 'commands.user1' or 'commands.user1Until' fields in an alarm zone to get here rather than building it yourself.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/user1", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/user1") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/user1' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/user1', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/user1', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/user1', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Change an alarm zone to user2
Sends an override to an alarm zone to set its state to 'user2', the other of of the custom states.
If you send an end time in the body, and the alarm zone has a schedule to consult to find the state that it should return to, the override will only stay in effect until then.
If you do not, the override will remain in place until the next scheduled or manual change.
Follow one of the 'commands.user2' or 'commands.user2Until' fields in an alarm zone to get here rather than building it yourself.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/user2", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/user2") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/user2' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/user2', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/user2', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/user2', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Arm an alarm zone (high voltage)
Sends an override to an alarm zone to set its state to 'armed - high voltage'.
If you send an end time in the body, and the alarm zone has a schedule to consult to find the state that it should return to, the override will only stay in effect until then.
If you do not, the override will remain in place until the next scheduled or manual change.
Follow one of the 'commands.highVoltage or 'commands.highVoltageUntil' fields in an alarm zone to get here rather than building it yourself.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Arm an alarm zone (low feel)
Sends an override to an alarm zone to set its state to 'armed - low feel'.
If you send an end time in the body, and the alarm zone has a schedule to consult to find the state that it should return to, the override will only stay in effect until then.
If you do not, the override will remain in place until the next scheduled or manual change.
Follow one of the 'commands.lowFeel or 'commands.lowFeelUntil' fields in an alarm zone to get here rather than building it yourself.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Cancel mode override
Cancels an override, returning the alarm zone to its scheduled state.
Follow the 'commands.cancel' field in an alarm zone to get here rather than building it yourself.
Parameters
idstringrequiredpathThe ID of the alarm zone.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding alarm zones (such as 'Override').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/alarm_zones/{id}/cancel", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/alarm_zones/{id}/cancel"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/alarm_zones/{id}/cancel'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/cancel', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/cancel', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/alarm_zones/{id}/cancel')
data = response.json()Monitor an alarm zone
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in an alarm zone summary or details pages to get here.
Parameters
idstringrequiredpathThe ID of the alarm zone.
fieldsstringstatusstatusTextstatusFlagsqueryThis instructs the server to return these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.
Response
Success. The introduction describes the three status fields.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an alarm zone, or the operator does not have a privilege on the zone's division that allows viewing alarm zones, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/alarm_zones/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/alarm_zones/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/alarm_zones/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/alarm_zones/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/alarm_zones/{id}/updates')
data = response.json(){
"updates": {
"status": "Disarmed.",
"statusText": "Disarmed.",
"statusFlags": [
"disarmed"
]
},
"next": {
"href": "https://localhost:8904/api/alarm_zones/328/updates/9_1"
}
}Card encoding
These routes allow clients to get Gallagher card encoding data for MIFARE DESFire and MIFARE Classic cards.
- Following features are required in the licence file:
- RESTCardholders
- RESTEncoding
- Encoding
- Photo-ID
- The REST Client operator should have following privileges:
- View Cardholders
- Print/Preview & Encode Card
- Allow Re-Printing and Re-Encoding (optional, depending on whether you want to allow re-encoding of already-encoded cards)
- Bytes are encoded as hex strings in decrypted encoding data.
- Sensitive data is encrypted in responses.
- Using the client certificate for authentication is recommended.
Configuration End to End Encryption
- Using openssl to generate ECDH_P384 public/private key pair
openssl ecparam -name secp384r1 -genkey -noout -out private.pem
openssl ec -in private.pem -pubout -outform DER | tail -c 97 | head -c 97 | xxd -p -c 97
openssl req -new -x509 -key private.pem -out E2EEncryptionKey.crt -days 365 -subj "/CN=Command Center REST Client E2E Encryption Key"
openssl pkcs12 -export -out key.pfx -inkey private.pem -in E2EEncryptionKey.crt- Store the private key securely on the encoding client side, e.g, storing it in Windows Certificate Store.
Import-PfxCertificate -FilePath "path\to\key.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password (Read-Host "Enter PFX Password" -AsSecureString)- Open the REST Client settings in Configuration Client, put the X9.63 hex format of public key to the Remote Public Key input in E2E Key tab.
Code Example for Client-Side Decryption
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
// Load private key from Windows Certificate Store
// Open the Current User store
using X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
// Find the certificate by Subject Name
X509Certificate2? cert = store.Certificates
.Find(X509FindType.FindBySubjectDistinguishedName, "CN=Command Center REST Client E2E Encryption Key", false)
.FirstOrDefault();
if (cert == null)
{
Console.WriteLine("Error: Certificate not found.");
return;
}
// 3. Get The Private Key
ECDiffieHellman? privateKey = cert.GetECDiffieHellmanPrivateKey();
if (privateKey == null)
{
throw new Exception("Certificate found, but private key is not accessible or not ECDH.");
}
Console.WriteLine($"Successfully retrieved key. Curve: {privateKey.ExportParameters(false).Curve.Oid.FriendlyName}");
// Simulated API response (replace with actual API call and response content)
var apiResponseJson = @"{
""encryptedEncodingData"": {
""cipherText"": ""<base64-encoded cipher text>"",
""iv"": ""<base64-encoded IV>"",
""ephemeralPublicKey"": ""<base64-encoded ephemeral public key>"",
""tag"": ""<base64-encoded authentication tag>""
},
""callback"": {
""encodeCompleted"": {
""href"": ""https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted?token=abc"" },
""encodeFailed"": {
""href"": ""https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeFailed?token=abc"" }
}
}";
var response = JsonSerializer.Deserialize<EncryptedEncodingDataResponse>(apiResponseJson, options);
if (response == null)
{
throw new Exception("Cannot deserialize response.");
}
// Derive the shared secret
var encryptedData = response.EncryptedEncodingData;
using var ephemeralKey = ECDiffieHellman.Create();
ephemeralKey.ImportSubjectPublicKeyInfo(encryptedData.EphemeralPublicKey, out _);
byte[] sharedSecret = privateKey.DeriveKeyFromHash(ephemeralKey.PublicKey, HashAlgorithmName.SHA512);
byte[] aesKey = sharedSecret.Take(32).ToArray();
byte[] hmacKey = sharedSecret.Skip(32).Take(32).ToArray();
// Verify HMAC tag to ensure data integrity and authenticity before decryption
using var hmac = new HMACSHA256(hmacKey);
var contentToSign = encryptedData.IV
.Concat(encryptedData.CipherText)
.Concat(encryptedData.EphemeralPublicKey) // Include ephemeral key to bind it to this session
.ToArray();
byte[] computedHash = hmac.ComputeHash(contentToSign);
if (!computedHash.SequenceEqual(encryptedData.Tag))
{
throw new Exception("Error: HMAC tag verification failed. Data may use a different signing order or has been tampered with.");
}
// Decrypt the cipher text using AES-CBC with the shared secret as the key and the provided IV
using var aes = Aes.Create();
aes.Key = aesKey;
aes.IV = encryptedData.IV;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using var decryptor = aes.CreateDecryptor();
byte[] decryptedData = decryptor.TransformFinalBlock(encryptedData.CipherText, 0, encryptedData.CipherText.Length);
// Convert decryptedData to string
string decryptedJson = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine("Decryption successful. Decrypted data:\n" + decryptedJson);
// Deserialize decrypted JSON to get the actual encoding data structure (e.g., DESFireEncodingResponse or ClassicEncodingResponse)
...
public record EncryptedEncodingData(byte[] CipherText, byte[] IV, byte[] EphemeralPublicKey, byte[] Tag);
public record Link(string Href);
public record CallbackLinks(Link EncodeCompleted, Link EncodeFailed);
public record EncryptedEncodingDataResponse(EncryptedEncodingData EncryptedEncodingData, CallbackLinks Callback);Get encoding payload for a DESFire card
This returns the data you need to encode a Gallagher credential to a MIFARE DESFire card.
POST the card's CSN (UID) and, if present on-card, the contents of the Gallagher Application Directory (CAD) files. The response contains the encrypted DESFire applications, keys, files, and updated CAD files to write, and callback links are in plain text for you to report the success of your encoding.
Encode applications in the order they are returned so that the card remains usable if encoding fails part-way through.
Body
csn: Card serial number (UID).cadFiles: Gallagher Application Directory file contents as hex. Required if CAD files exist on card.reason: Reason for encoding, e.g. "New card issuance", "Lost card replacement". Optional.
csnHexStringrequiredcadFilesobjectOptional data read from CAD files, if present on the card.
reasonstringParameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Returns encrypted DESFire encoding data and callback links.
The encryptedEncodingData field contains encrypted payload that, when decrypted, has
the structure of DESFireEncodingResponse with
applications, keys, and files.
Possible reasons:
- The request body is invalid.
- There is no free application ID left on the card.
- The card is not of an encodable type. In API terms, its
credentialClassneeds to becard. - The reason for encoding is too long (over 100 characters).
The response body will contain an error message.
Possible reasons:
- Command Centre does not have a licence for the encoding API.
- The operator lacks the necessary privilege.
- Re-encoding is disabled for that card type and the card has already been encoded.
The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.
Possible reasons:
- Config Client is not configured with end to end encryption keys so it cannot encrypt the encoding data to return in the response.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire' \
-H 'Content-Type: application/json' \
-d '{
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}
response = requests.post('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire', json=payload)
data = response.json(){
"csn": "0x04A23F9C123456",
"cadFiles": {
"0": "0x00C34F2081F4000000000000000000000000000000000000000000000000000000000000"
},
"reason": "New card issuance"
}{
"encryptedEncodingData": {
"cipherText": "a8G79CeiZPIgm3nZaAdE6ACIjivuHdOVWHB/nxO+xWOMXLXbVnObOSPVwCDvUwIk97OVZi7gahBgjZPcy4D0TeQJ6YUMzNuwB50EsGkeu+UWD640Hmwq7WGGuchXAZP4bSw6DrrSvwSh",
"iv": "2XpAcIq4nfKzamPMlCo36g==",
"ephemeralPublicKey": "BEK2nvjPU3+fZ9DjU2xmy1+zWI0wCGOzX1bCi4mnpaKbmsI9fEMgitljCA+aJRSZWvCBhzIn9vQswwni5F9dOg30tjrW",
"tag": "N/NNXOvJlvUm+UgPQVufM7w/t0dfvcdluc6qOUgRFio="
},
"callback": {
"encodeCompleted": {
"href": "https://localhost:8904/api/cardholders/123/cards/e3f8c7/encodeCompleted?token=a9f3b7"
},
"encodeFailed": {
"href": "https://localhost:8904/api/cardholders/123/cards/e3f8c7/encodeFailed?token=a9f3b7"
}
}
}This shows what the encryptedEncodingData field contains after client-side decryption. This is NOT the actual API response format.
{
"applications": [
{
"id": "0xF48120",
"name": "gallagher",
"keys": [
{
"id": 0,
"name": "applicationKey",
"data": "0xEE076105634A47AF70A0678CE86FE610",
"version": 1
},
{
"id": 1,
"name": "uidDiscoveryKey",
"data": "0x6C957D11151200A6492E27819D345DDA",
"version": 1
},
{
"id": 2,
"name": "applicationReadKey",
"data": "0xF2428C4A0B3C8F81B7DB2E76370F73EA",
"version": 1
}
],
"keySettings1": "0x0B",
"keySettings2": "0x83",
"files": [
{
"id": 0,
"data": "0xA396A32BA380A3375C695CD45C7F5CC8",
"accessRights": "0x0020",
"communicationMode": "0x03"
},
{
"id": 1,
"data": "0xA4F2276D46633FA29499B6BC781D8874",
"accessRights": "0x0020",
"communicationMode": "0x03"
}
]
},
{
"id": "0xF4812F",
"name": "gallagherApplicationDirectory",
"keySettings1": "0x0B",
"keySettings2": "0x81",
"keys": [
{
"id": 0,
"name": "applicationKey",
"data": "0xbeef",
"version": 1
}
],
"files": [
{
"id": 0,
"data": "0x00C34F2081F4000000000000000000000000000000000000000000000000000000000000",
"accessRights": "0x00E0",
"communicationMode": "0x00"
}
]
}
],
"cardMasterKey": "0x0BB97387F6CA437AAF35A4A915441D42",
"defaultAppKey": "0xD712BDCE90D845D88DA0A80081F324600000000000000000"
}Get encoding payload for a Classic card
This returns the data you need to encode a Gallagher credential to a MIFARE Classic card.
POST the card's CSN (UID) and card directory data (CAD/MAD) if present, plus any unavailable sectors. The response contains the sectors and blocks to write, and callback links for you to report the success of your encoding.
Encode applications in the order they are returned so that the card remains usable if encoding fails part-way through.
Body
csn: Card UID.cad: Gallagher Application Directory sector/blocks (required if present on card).mad: MAD bytes (required if present on card).unavailableSectors: Sectors already occupied or reserved.reason: Reason for encoding, e.g. "New card issuance", "Lost card replacement". Optional.
csnHexStringrequiredcadClassicCadmadClassicMadunavailableSectorsArray<integer>reasonstringParameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Returns callback links and encrypted Classic encoding data.
The encryptedEncodingData field contains encrypted payload that, when decrypted,has
the structure of ClassicEncodingResponse with
sectors and blocks.
Possible reasons:
- The request body was invalid.
- There are no free sectors left on the card.
- The card is not of an encodable type. In API terms, its
credentialClassneeds to becard. - The reason for encoding is too long (over 100 characters).
Possible reasons:
- Command Centre does not have a licence for the encoding API.
- The operator lacks the necessary privilege.
- Re-encoding is disabled for that card type and the card has already been encoded.
The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.
Possible reasons:
- Config Client is not configured with end to end encryption keys so it cannot encrypt the encoding data to return in the response.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic' \
-H 'Content-Type: application/json' \
-d '{
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}
response = requests.post('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic', json=payload)
data = response.json(){
"csn": "0x04A23F9C",
"cad": {
"sector": 14,
"blocks": {
"0": "0xadad",
"1": "0xafad"
}
},
"mad": {
"blocks": {
"1": "0x0ebd",
"2": "0x0816"
}
},
"unavailableSectors": [
2,
3,
4
],
"reason": "Lost Card Replacement"
}{
"encryptedEncodingData": {
"cipherText": "a8G79CeiZPIgm3nZaAdE6ACIjivuHdOVWHB/nxO+xWOMXLXbVnObOSPVwCDvUwIk97OVZi7gahBgjZPcy4D0TeQJ6YUMzNuwB50EsGkeu+UWD640Hmwq7WGGuchXAZP4bSw6DrrSvwSh",
"iv": "2XpAcIq4nfKzamPMlCo36g==",
"ephemeralPublicKey": "BEK2nvjPU3+fZ9DjU2xmy1+zWI0wCGOzX1bCi4mnpaKbmsI9fEMgitljCA+aJRSZWvCBhzIn9vQswwni5F9dOg30tjrW",
"tag": "N/NNXOvJlvUm+UgPQVufM7w/t0dfvcdluc6qOUgRFio="
},
"callback": {
"encodeCompleted": {
"href": "https://localhost:8904/api/cardholders/123/cards/e3f8c7/encodeCompleted?token=a9f3b7"
},
"encodeFailed": {
"href": "https://localhost:8904/api/cardholders/123/cards/e3f8c7/encodeFailed?token=a9f3b7"
}
}
}This shows what the encryptedEncodingData field contains after client-side decryption. This is NOT the actual API response format.
{
"sectors": [
{
"id": 15,
"name": "gallagher",
"blocks": [
{
"id": 0,
"file": "0xA396A35AA380A31B5C695CA55C7F5CE4"
},
{
"id": 1,
"file": "0x7777772E63617264617E2E636F6D2020"
},
{
"id": 2,
"file": "0x00000000000000000000000000000000"
},
{
"id": 3,
"file": "0x00000000000078778800000000000000"
}
]
},
{
"id": 14,
"name": "gallagherApplicationDirectory",
"blocks": [
{
"id": 0,
"file": "0x1F34000102B670F00000000000000000"
},
{
"id": 1,
"file": "0x00000000000000000000000000000000"
},
{
"id": 2,
"file": "0x00000000000000000000000000000000"
},
{
"id": 3,
"file": "0x00000000000078778800000000000000"
}
]
},
{
"id": 0,
"name": "mad",
"blocks": [
{
"id": 1,
"file": "0xCD000000000000000000000000000000"
},
{
"id": 2,
"file": "0x00000000000000000000000011481248"
}
]
}
]
}Notify Command Centre that encoding succeeded
Notify Command Centre that encoding succeeded for the given credential.
Do not code this URL into your application. Take it from the 'callback' block that accompanied the card's encoding data in the previous API call.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
tokenstringrequiredqueryToken provided by the encoding-data response callback link.
Response
OK
Bad request: token is missing or not recognised.
The site does not have a licence for the Encoding API.
The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.
Gone - Callback token has expired or already been used
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted')
data = response.json()Notify Command Centre that encoding failed
Notify Command Centre that encoding failed for the given credential. Optionally include an error message.
Do not code this URL into your application. Take it from the 'callback' block that accompanied the card's encoding data in the previous API call.
Body
messagestringParameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
tokenstringrequiredqueryToken provided by the encoding-data response callback link.
Response
OK
Token is missing or not recognised, or the request body is invalid.
The site does not have a licence for the Encoding API.
The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.
Callback token has expired or already been used.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"message": "string"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"message": "string"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed' \
-H 'Content-Type: application/json' \
-d '{
"message": "string"
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"message": "string"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"message": "string"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"message": "string"
}
response = requests.post('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed', json=payload)
data = response.json(){
"message": "Insert any error message here."
}Cardholders
A cardholder is a user account.
These methods give you read and write access to the cardholder data in the Command Centre database. You can download cardholders, create them, search them by name or Personal Data Field (PDF) value, and work on them as you would in the interactive clients.
The first use case below introduces the main entry point. It is a paginated search interface
that gives you basic data for any number of cardholders. You will not receive many fields by
default, but you can ask for more using the fields query parameter.
A field called href serves both as a unique identifier for the cardholder and the location of
what we call his or her details page. You can submit an HTTP GET to that href to retrieve the
entire cardholder record, or an HTTP PATCH to update it, or an HTTP DELETE to remove it. Be
careful: there is no coming back from deleting a cardholder.
Write-locked cardholders
If an operator has unsaved changes on a cardholder object when your integration attempts to update it, your PATCH will fail with a 409 error. The message will tell you the operator and workstation that is holding the lock; we suggest your application logs that and tries its update again later.
Licensing
All of the API calls described here are available with the RESTCardholders licence. Licences that enable additional features are described in each route.
Cardholder use cases
Searching for cardholders by name
GET /api.Follow the link at
features.cardholders.cardholders.href↪, appending a search term to narrow the results and thefieldsparameter to add the fields you need. 3. Process the results, following thenextlink until there isn't one.
Downloading all cardholders
GET /apiFollow the link at
features.cardholders.cardholders.href↪. This is effectively a cardholder search with no filters. You should addsortandtopquery parameters to sort by ID and increase the number of cardholders per page: see the efficiency tips!Process the bundle of cardholders in the result.
Follow the link at
next.hrefif there is one, and repeat.
Synchronising a user directory
This is a common use case, described in the section on Cardholder changes. It shows how to:
- Get a bookmark at the head of the queue of cardholder changes.
- Do a one-off sync of all cardholders.
- Loop to stay up to date, starting with the bookmark.
That is how Gallagher's cardholder-synchronising integrations work.
Searching for cardholders by Personal Data Field value
Say you want to find a cardholder with a particular employee ID, and Command Centre is holding that in a personal data field called "employee_ID".
First, reconsider, and look at the 'Synchronising a user directory' use case above. If you are going to be doing this for many cardholders, GETting cardholders in bulk and filtering out the interesting ones client-side is more efficient than a large number of unique PDF searches. You don't have to get all cardholders; you could get all cardholders in one division, or all cardholders with a non-blank value for your PDF (shown below), or a combination. It will be quicker than a sequence of searches that return one cardholder each.
Before you can start a cardholder search, you must find the PDF's identifier.
GET /api.v8.10 or later: follow the link at
features.personalDataFields.personalDataFields.href↪, appending the queryname="employee_id". This will return every PDF in the system with that name. There will be more than one, in rare cases.Earlier versions: follow the link at
features.items.items.href↪, appending the queryname="employee_id"(after a?or&, of course). This will return every item in the system with that name. Pick the one with a type name of 'Personal Data Field'. Or you could appendtype=33to the item search: you will only be shown PDFs.Note the ID of the item. It will be a short alphanumeric.
Case does not matter when searching by name but remember the quotes: Command Centre will perform a substring match if you omit them, which is vastly slower and (for the example above) return you the PDFs 'previous_employee_ID' and 'employee_ID_allocated_flag', if you have PDFs with those names.
Now you can use that PDF ID in a cardholder search.
- Recall the JSON object you received from
GET /api. - Follow the link at
features.cardholders.cardholders.href↪, adding a query separator andpdf_<your_pdf_id>=<your_pdf_value>, without the angle brackets.
The results will only include cardholders who have <your_pdf_value> as a substring of
the Personal Data Field with ID <your_pdf_id>. Again, the search is
case-insensitive and you should bookend <your_pdf_value> with quotes if you want to anchor it at
each end. In our example we would GET /api/cardholders?pdf_345="EID8888".
Use a percent sign % for <your_pdf_value> if you want to see all cardholders who have a
non-blank value for that PDF.
The server will reply with a default set of fields for your cardholder. If they are not what
you'd like, you can use the fields query parameter to change them.
See the cardholder GET for more.
Creating a cardholder
GET /api.Use the link at
features.accessGroups.accessGroups.hrefto find the hrefs of access groups you wish to add your new cardholder to.Do the same for the competencies, relationships (
roles), lockers, PDF definitions, and cards/credentials (cardTypes) that your new cardholder needs, using other URLs in thefeaturessection of/api. In v8.10, the card types that your operator has the privilege to assign are at a new URL given in the fieldfeatures.cardTypes.assign.href↪.Compose a JSON body using this example and this detail, then send it in a POST to the href at
features.cardholders.cardholders.href↪ on the/apipage.
Modifying a cardholder
Find your cardholder using one of the processes above.
If you need the current values before you update them, use the
fieldsparameter to add fields (such as accessGroups, cards, or competencies) to the search results.PATCH the cardholder's href with a document describing the additions, deletions, and modifications you wish to make to the cardholder and his or her associations.
In its simplest form, your document could be a collection of key/value pairs much the same as you receive from a GET to the same href. More complex forms allow adding, modifying, and removing cards, competencies, group membership, PDFs, and relationships. Three examples follow.
Removing a cardholder from groups
Find your cardholder using one of the processes above.
Follow the link to their details page or add
fields=accessGroupsto the search. That will show all their access group memberships.PATCH the cardholder with the hrefs of the memberships you want to delete in an array called
removeinside a block calledaccessGroups.
The access groups section describes what to do when you want to start with a group and remove several cardholders from it.
Removing all group memberships, competencies, relationships, cards, or lockers
This shows how you could remove a cardholder from all his or her access groups, but you could just as simply apply the process to other fields that come back as arrays, such as competencies, relationships, operator groups, cards, and lockers.
GET your cardholder's details page, or search with
accessGroupsin the field list. Either way, you will receive all their access group memberships.Take the array at
accessGroups, rename it toremove, and put it in object calledaccessGroupsat the root level of a JSON object.PATCH the cardholder with that JSON object. That will remove the cardholder from all the groups that came back in the GET.
Assigning a new card to a cardholder
GET /api.Follow the link at
features.cardTypes.cardTypes.href↪ to find the href for the new card's type.If you want to create a card in a state other than the default, get the state from the same page.
Find the href for the cardholder using one of the search methods above.
Optional: follow the link at
edit.hrefon the cardholder page. If there is a link atupdate.href, your REST client has permission to modify the cardholder.PATCH the cardholder's href according to the cardholder patch schema.
Deleting a cardholder's PDF value
- Find the URL of your cardholder using one of the processes above.
- PATCH your cardholder with this in the body:
{ "@name_of_PDF": null }
Deleting a cardholder
- Find your cardholder using one of the processes above.
- Send an HTTP DELETE to the cardholder's href.
Updating a cardholder's location
Find the href of your target access zone using the link at
features.cardholders.updateLocationAccessZones↪ in the results ofGET /api.Find your cardholder using one of the processes above. If you are using search, add
updateLocationto thefieldsparameter to save you having to GET their details page later.Send an HTTP POST to that cardholder's
updateLocation.hreflink.
Finding which access zones and doors a cardholder can access
- GET your cardholder's details page, or search cardholders with
accessGroupsin the field list. Either way, you will receive all their access group memberships. - Iterate through all the access groups looking into their
accessblocks for access zones and schedules. Recurse up the access group hierarchy by following each group'sparent.href.
Now you have the cardholder's access zones. If you want doors:
- For each access zone, GET its href and look in its
doorsblock.
Finding recently-created cardholders
Informing you when Command Centre creates cardholders is a function of the Cardholder changes call, described there.
However for that to work you need to make API calls before and after the cardholders are
created. If they already exist, a workaround is to get a page of cardholders sorted by
database ID descending using sort=-id&top=10. Change the ten to the number of cardholders
you'd like, obviously. Because Command Centre allocates database IDs to new items in
increasing order, the first page you get will be the most-recently created cardholders. This
is not suitable for use in a production system! Use the cardholder changes call instead.
Field names in query parameters
The cardholder search and
details operations' fields parameter and the change
tracking operation's fields and filter parameters
take a list of field names. Other sections of this document describe how those parameters
affect the API calls, but their format is the same so this section will cover it once.
You can form the name of a field by joining the components of its JSON path with dots. Fields
at the root level of the cardholder object, such as firstName and @emailAddress (a PDF),
have just one component. Fields that are one level down, such as accessGroups.status, have
two.
Treat the string matches as case sensitive: use lastName rather than lastname.
The string must not contain any spaces. Just alphanumerics, underscores, commas, and dots.
To serve as examples, this is the list you can choose from in 8.30:
href, id, firstName, lastName, shortName, description, authorised,
lastSuccessfulAccessTime, lastSuccessfulAccessZone, division,
personalDataFields, cards, accessGroups, competencies, notes, notifications,
relationships, lockers,
cards.href, cards.number, cards.from, cards.until, cards.cardSerialNumber, cards.type,
cards.status, cards.invitation, cards.issueLevel, cards.pivData,
cards.pivData.chuid, cards.pivData.pivStatus, cards.pivData.lastCheckTime,
cards.pivData.chuid.hash, cards.pivData.chuid.fascn, cards.pivData.chuid.duns,
cards.pivData.chuid.orgIdentifier,
cards.invitation.href, cards.invitation.singleFactorOnly, cards.invitation.email,
cards.invitation.mobile,
notifications.enabled, notifications.from, notifications.until,
accessGroups.href, accessGroups.accessGroup, accessGroups.from,
accessGroups.until, accessGroups.status,
relationships.href, relationships.cardholder, relationships.role,
lockers.href, lockers.locker, lockers.from, lockers.until
Later versions of Command Centre added many more. See the details operation for the complete list of cardholder fields.
A special value default applies a default set of fields that varies with the call you're
making.
personalDataFields is also special. It will give you the personalDataDefinitions block plus
all the PDF values at the root level with their names preceded by '@'-signs.
Licensing
All of the API calls described here are available with the RESTCardholders licence, with the following exceptions and notes:
Visitor management calls need both RESTCardholders and VisitorManagement.
Creating, modifying, and deleting cardholders' connections to competencies requires the RESTConfiguration licence.
Creating, modifying, and deleting competency items requires the RESTConfiguration licence.
Managing access group memberships is enabled by the RESTCardholders licence but managing access group items, card types, personal data definitions, and roles will require the RESTConfiguration licence. These features are coming soon.
Lockers and locker banks are also available with the RESTStatus licence, and a subset of their fields (enough to allow overriding them) are available with the RESTOverrides licence.
The server will return a 403 if you attempt an operation for which the server is not licensed.
Versions
The body of this document clearly indicates when recent features arrived in the API so that readers with older versions of Command Centre know not to expect them.
Search cardholders
This call returns cardholders matching your search criteria.
The result will contain no more than 100 or 1000 cardholders depending on your version; you
should follow the next link, if it is present, to collect the next batch.
When you have loaded all the cardholders there will be no next link.
If your result set is empty it means your operator does not have the privilege to view any cardholders. Perhaps there are none in the divisions in which your operator has privileges, or your operator has no privileges at all.
Adding or modifying cardholders between calls to this API will not affect the pagination of its results if you sort by ID.
Do not code this URL into your application. Take it from the href field in the
features.cardholders.cardholders section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1querySets maximum number of cardholders to return per page.
Older versions of Command Centre returned 100. That is acceptable for a GUI application that will only display the first page of cardholders, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
Version 8.70 will return 1000 items per request by default. 1000 is about where a graph of performance versus page size begins to level out. You may see some improvement by taking it even higher.
namestringqueryLimits the results to cardholders with a name that matches this string. By default, it is
a substring search against the first name or the last name or the concatenation
'lastName, firstName'; surround the parameter with double quotes "..." for an
exact search.
Without quotes, a percent sign % inside your search string will anchor the search at
both ends (so it will no longer be a substring search) and the % will match any
substring. For example, boothroyd,% will only match cardholders whose last name is
Boothroyd.
The search is always case-insensitive. Results are undefined if you do a substring search
for the empty string (name=). You will receive no cardholders if you search for those
with no name (name=""), as all items must have a name.
Because a plus sign + represents a space in a query string, replace each plus sign in
your search string with %2d.
The search parameters form a logical conjunction. They are ANDed
together. Therefore if you search for name=Mary&pdf_1315=nanny you will only get back
cardholders with 'Mary' in their name and 'nanny' in the PDF with ID 1315.
pdf_{id}stringqueryLimits the results to cardholders with a value for the Personal Data Field with this ID that matches the parameter.
It is a substring match by default; surround it with double quotes "..." for an exact
match. Tests showed an exact match to be 100x quicker than a substring search on a large
database.
Without quotes, _ will match any single character and % will match any substring.
Having either in your term will anchor the string at both ends so it will not be a
substring search. A lone % will return any cardholder who has this PDF set to a
non-null value.
Searching for a blank value using pdf_xxx="" or pdf_xxx= currently matches no
cardholders, which is not useful. Do not rely on that behaviour since we will change it
in a future version of Command Centre to return cardholders with no value for that PDF.
Because a plus sign + represents a space in a query string, turn plus signs in your
string into %2d.
The search is always case-insensitive.
Search parameters form a logical conjunction. They are ANDed
together. Therefore the search pdf_1315=nanny&pdf_1315=paratrooper will only return
cardholders whose PDF 1315 contains the strings 'nanny' and 'paratrooper'.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
accessZoneArray<string>queryLimits the results to cardholders who are in one of the access zones with the given IDs. Do not put quotes around the IDs, and separate them with commas.
To get everyone who is in any access zone use accessZone=*. It will return all
cardholders who have badged at least once and who are not currently 'outside the system'.
A cardholder is 'outside' if they badge through a door that has no access zone configured for that direction of travel,
or if an operator manually moves them outside.
fieldsArray<string>defaultsquerySpecifies the fields you want in the search results. The values you can use here are the
same as you can for the details page.
Using it you can return everything on the search page that you would find on the details
page with the exception of the edit and updates links. Separate values with commas.
Use the value cards.encodingData to receive links to the methods that return
card-encoding data.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Obviously only do that if you have more to add.
Use the special value personalDataFields to return the personalDataDefinitions block
as well as the PDF values at the root level of the cardholder object.
Use the special value pdf_XXX (where XXX is the ID of a PDF definition) to include
just that PDF. If you do not have the PDF's ID, and don't mind the performance hit of
retrieving all the PDFs, using personalDataDefinitions may be simpler.
There is a special value defaults which adds everything you would have received had you
not sent a fields parameter at all, but we advise against its use: specifying every
field you want makes the response more predictable across different server versions.
Treat the string matches as case sensitive: use 'lastName' rather than 'lastname'.
In v8.00 you will receive the href and internal ID even if you do not ask for them. In 8.10 you will not. If you are going to send the fields parameter and need the href or ID, include them.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The server could not make sense of your search terms.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/cardholders", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/cardholders"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/cardholders'const response = await fetch('https://127.0.0.1:8904/api/cardholders', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/cardholders')
data = response.json(){
"results": [
{
"href": "https://host.com:8904/api/cardholders/325",
"id": "325",
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true
}
],
"next": {
"href": "https://host.com:8904/api/cardholders?skip=61320"
}
}Create a cardholder
This creates a new cardholder, including his or her cards, group memberships, personal data, competencies, and roles.
Do not code this URL into your application. Take
it from the href field in the features.cardholders.cardholders section of /api.
The POST expects a document in the same format as the the cardholder detail. Many fields are optional, of course, and some (like the last successful access time) do not make sense when creating a cardholder. See the cardholder POST example for details.
You will achieve better performance if you combine all you want to achieve into one POST, rather than creating the cardholder bare with a POST then adding cards, groups, PDFs, etc., with PATCHes later.
When successful it returns a location header containing the address of the new cardholder.
Note that you can only create one cardholder per POST.
Body
This can be a large object as shown in the body model, or a tiny one, because the only fields you must have in the POST are the division and either the first or last name.
This is part of the model of a POST body that creates a cardholder. It does not contain every field you can set when creating a cardholder, but shows the division (which is mandatory) and an access group, an access card, a supervisor, a competency, two personal data fields, two lockers, and two elevator groups.
There are more fields on a cardholder than shown here. For a complete list please see the schema for the detailed cardholder object that you receive from a cardholder href.
firstNamestringYou must supply either this or the last name when creating a cardholder.
The maximum length of a cardholder's first name in version 9.50 is 50 characters.
lastNamestringYou must supply either this or the first name when creating a cardholder.
The maximum length of a cardholder's last name in version 9.50 is 50 characters.
shortNamestringdescriptionstringauthorisedbooleanRemember to set this true, as shown here in the POST, or later in a PATCH. Otherwise your new cardholder will never get through a door.
divisionobjectrequiredMandatory when creating any cardholder. In this example, we want all students in division 5387.
@emailstringAn example PDF value. In this example, access group 352 must include a PDF called 'email' otherwise this will appear to have no effect.
@headshotstringAn example image PDF encoded to Base64. Access group 352 must include this PDF as well.
As a quick visual check on your encoding, JPEGs start with /9j/.
personalDataDefinitionsArray<CardholderPDF>This is how you set and unset the notifications flags on PDFs. If you do not do it here when you first give a cardholder a PDF, it will come from the PDF's definition.
cardsArray<CardholderCard>This example creates one physical card of type 600 with a PIN and a system-generated card number, and another of type 654 which--judging by the invitation block--must be a mobile credential. Command Centre will send an invitation to Nick at those coordinates.
Card PINs were added to the API in 8.90.
accessGroupsArray<CardholderAccessGroup>Here you can add the access groups necessary to give your new cardholder the PDFs and access he or she needs. In this example we set the activation date of the access group membership to the first of January 2019.
operatorGroupsArray<CardholderOperatorGroup>Here you can add the operator groups necessary to give your new cardholder the software access he or she needs. Added in 8.50.
competenciesArray<CardholderCompetency>In this example we are giving our new cardholder a disabled competency, set to enable in January 2019.
notesstringnotificationsobjectYou can set or update any of the three fields in this block.
relationshipsArray<CardholderRelationship>Here you would set the cardholders who will perform roles for this cardholder.
The example shows that this cardholder will have cardholder 5398 performing role 5396.
Remember that you can supply an array of these objects if your cardholder is having more than one role filled.
lockersArray<CardholderLocker>Here you set all the cardholder's locker assignments. Ensure you are only attempting to give your cardholder a locker according to site policy (one locker per locker bank, for example), otherwise the POST will fail.
The example shows our cardholder receiving two lockers.
elevatorGroupsArray<CardholderElevatorGroup>Here you set all the new cardholder's default elevator floors and passenger types. Passenger type properties are false by default.
The example shows our cardholder receiving a default floor for the first elevator group, with the Code Blue feature enabled in the second group.
Added in 8.50.
Response
Success.
The body of the POST did not describe a valid cardholder.
If you see 'Data has not been entered for this Personal Data Field' in the response body, you have attempted to create a cardholder in an access group that has a required Personal Data Field, but not supplied a value for that PDF.
If you see 'Invalid cardholder', the server could not parse the JSON in the body of your POST. Remember to quote all strings, especially those than contain @ symbols.
The operator does not have a privilege that allows creating cardholders, or you
attempted to set a field for which the operator has no privilege (probably notes), or
the server has reached its licensed limit of cardholders.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders' \
-H 'Content-Type: application/json' \
-d '{
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick'\''s mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": True,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": True
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": False,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": True,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": True
}
]
}
response = requests.post('https://127.0.0.1:8904/api/cardholders', json=payload)
data = response.json()This is an example of a POST you could use to create a cardholder in a specific division and access group, with an access card, another cardholder as a supervisor, a competency, a student ID and a photo held in personal data fields, two lockers, and configuration for two elevator groups. There are more fields on a cardholder than shown in this example. For a complete list please see the schema for the detailed [cardholder object](#definition-CardholderDetail) that you receive from a cardholder href. In this example we are giving our new cardholder a disabled competency, set to enable in January 2019. The example shows that this cardholder will have cardholder 5398 performing role 5396. The example shows our cardholder receiving two lockers. The example shows our cardholder receiving a default floor for the first elevator group, with the Code Blue feature enabled in the second group.
{
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}This is an example of what you would send to create a new cardholder with two mobile credentials. `division` and either `firstName` or `lastName` are required on a new cardholder, so this example includes those. `type` is the only compulsory field in the `cards` blocks. It must contain an href to the card type of the new mobile credential. The so-called card "number" on a mobile credential does not need to be numeric. The `status` block is optional. Provide a field `value` inside it set to one of the valid starting states for the mobile credential. If you omit it, the credential will start in the default state. `invitation` is a block containing fields that describe how Command Centre should set about registering the mobile device. If you specify `mobile` you must also supply `email`. If you give neither, Command Centre will not send an invitation to the cardholder. `singleFactorOnly` defaults to false, which is the recommended setting. See the [card detail](#definition-CardholderCard) for more.
{
"firstName": "Nick",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"cards": [
{
"number": "Nick's mobile",
"status": {
"value": "active"
},
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2018-01-01T00:00:00.000Z",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789",
"singleFactorOnly": true
}
},
{
"number": "Nick's other mobile",
"status": {
"value": "active"
},
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"invitation": {
"email": "nick@example.com",
"mobile": "02198765432",
"singleFactorOnly": true
}
}
]
}This is a minimal object for creating a cardholder with one card, containing the only required fields for a cardholder (name and division) and the only required field for a card (the card type). See the [card detail](#definition-CardholderCard) for other fields you can use, such as the card's issue level and from/until dates. `type` is the only compulsory field in a new card. It must contain an href to the card type of the new card.
{
"firstName": "Nick",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
}
}
]
}Get details of a cardholder
This retrieves one cardholder from Command Centre. It does not return all the fields
available (there are too many) so, much like the cardholder search, it accepts a fields
parameter where you specify the fields you need.
Do not use a cardholder's ID to build the URL yourself: follow the href in the cardholder search to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>defaultsquerySpecifies the fields you want in the results. The values you can list are the same as the field names in the detail results. Use it to return fewer fields than normal. Separate values with commas.
Treat the string matches as case sensitive: use 'lastName' rather than 'lastname'.
Added to the cardholders controller in 8.00. In that version you will receive the href, internal ID, and updates link even if you do not ask for them. In 8.10 you will not. If you are going to send the fields parameter and need those fields, include them.
Response
Success.
That is not the URL of a cardholder, or the operator does not have the privilege to view that cardholder.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/cardholders/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/cardholders/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/cardholders/{id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/cardholders/{id}')
data = response.json(){
"href": "https://host.com:8904/api/cardholders/325",
"id": "325",
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"disableCipherPad": false,
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00.000Z",
"until": "2017-10-17T14:59:00.000Z"
},
"oidcLoginEnabled": true,
"oidcUserId": "admin_qmaster@misix.gov.uk",
"operatorLoginEnabled": true,
"operatorUsername": "qmaster",
"operatorPasswordExpired": false,
"personalDataDefinitions": [
{
"@Student ID": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2356",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/2356",
"name": "Student ID",
"id": "2356",
"type": "string"
},
"value": "8904640"
}
},
{
"@Photo": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2369",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/2369",
"name": "Photo",
"id": "2369",
"type": "image"
},
"value": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2369"
}
}
}
],
"windowsLoginEnabled": true,
"windowsUsername": "misix.local\\qmaster",
"lastSuccessfulAccessTime": "2004-11-18T19:21:52.000Z",
"lastSuccessfulAccessZone": {
"href": "https://host.com:8904/api/access_zones/333",
"name": "Twilight zone"
},
"serverDisplayName": "ruatoria.satellite.int",
"usercode": "numeric, and write-only",
"operatorPassword": "write-only",
"cards": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9",
"number": "1",
"cardSerialNumber": "045A5769713E80",
"issueLevel": 1,
"status": {
"value": "Disabled (manually)",
"type": "inactive"
},
"type": {
"href": "https://host.com:8904/api/card_types/354",
"name": "Card type no. 1"
},
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789",
"singleFactorOnly": false,
"status": "sent",
"href": "https://security.gallagher.cloud/api/invitations/abcd1234defg5678"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z",
"credentialClass": "mobile",
"trace": false,
"lastPrintedOrEncodedTime": "2020-08-10T09:20:50.000Z",
"lastPrintedOrEncodedIssueLevel": 1,
"lastUsedTime": "2025-09-24T14:00:00.000Z",
"pin": "153624",
"visitorContractor": false,
"ownedBySite": false,
"credentialId": "reserved",
"bleFacilityId": "reserved",
"encodingData": {
"mifareClassic": {
"href": "string"
},
"mifareDesfire": {
"href": "string"
}
}
}
],
"accessGroups": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/D714D8A89F",
"accessGroup": {
"name": "R&D special projects group",
"href": "https://host.com:8904/api/access_groups/352"
},
"status": {
"value": "Pending",
"type": "pending"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z"
}
],
"operatorGroups": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD",
"operatorGroup": {
"name": "Locker admins",
"href": "https://host.com:8904/api/operator_groups/532"
}
}
],
"competencies": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3p0",
"competency": {
"href": "https://host.com:8904/api/competencies/2354",
"name": "Hazardous goods handling"
},
"status": {
"value": "Pending",
"type": "pending"
},
"disabled": true,
"enabled": true,
"expiryWarning": "2017-03-06T15:45:00.000Z",
"expiry": "2017-03-09T15:45:00.000Z",
"enablement": "2018-03-09T15:45:00.000Z",
"comment": "CPR refresher due March.",
"limitedCredit": true,
"credit": 37
}
],
"edit": {
"href": "https://host.com:8904/cardholders/325/edit"
},
"updateLocation": {
"href": "https://host.com:8904/api/cardholders/402/update_location"
},
"relationships": [
{
"href": "https://host.com:8904/api/cardholders/325/relationships/179lah1170",
"role": {
"href": "https://host.com:8904/api/roles/5396",
"name": "Supervisor"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398",
"name": "Miles Messervy",
"firstName": "Miles",
"lastName": "Messervy"
}
}
],
"lockers": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/t1m4",
"locker": {
"name": "Bank A locker 1",
"shortName": "A1",
"lockerBank": {
"href": "https://host.com:8904/api/locker_banks/4567",
"name": "Bank A"
},
"href": "https://host.com:8904/api/lockers/3456"
},
"from": "2017-01-01T00:00:00Z",
"until": "2018-12-31T00:00:00Z"
}
],
"elevatorGroups": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/567",
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635",
"name": "Main building lower floors"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637",
"name": "Lvl 1 lift lobby"
},
"enableCaptureFeatures": true,
"enableCodeBlueFeatures": false,
"enableExpressFeatures": true,
"enableServiceFeatures": false,
"enableService2Features": true,
"enableService3Features": true,
"enableVipFeatures": false
}
],
"updates": {},
"redactions": [
{
"href": "https://localhost:8904/api/cardholders/redactions/625",
"type": "normalEvents",
"when": "2023-01-01T00:00:00Z",
"before": "2022-01-01T00:00:00Z",
"status": "pending",
"redactionOperator": {
"name": "REST Operator",
"href": "https://localhost:8904/api/items/100"
}
}
],
"accessibilities": [
{
"type": "useExtendedAccessTime",
"enabled": true,
"from": "2026-08-15T21:00:00Z"
},
{
"type": "disableCipherPad",
"enabled": false
},
{
"type": "pinExemption",
"enabled": false
}
]
}Remove a cardholder
This call removes a cardholder from Command Centre. You can find the URL in the href in the cardholder search or any of the other calls that return cardholder hrefs. Do not build it yourself.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Success.
Deleting the cardholder failed. This happens when the cardholder is a critical part of another construct (a personalised notification, for example).
The operator does not have permissions to delete that cardholder or the server is not licensed for cardholder operations.
In versions up to and including 9.20 403 also means that there is no such cardholder.
New in 9.30. There is no such cardholder.
The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}')
data = response.json()Update a cardholder
This is the call you use to update a cardholder, including:
changing general properties such as names, division, and description,
changing PDF values, and
adding or updating cards, access group membership, roles and relationships, lockers, and competencies.
When changing a cardholder's division at the same time as PDFs or competencies, the extra privilege checks required for PDFs and competency changes use the origin division, not the destination division. So if you are moving a cardholder from a division in which your operator has no access to PDFs and competencies into one in which it does, first PATCH the cardholder into the new division then PATCH it with the other changes.
You can find this URL in the href in the cardholder search or any of the other calls that return cardholder hrefs. Do not build it yourself.
Note that the REST API does not implement the full suite of Command Centre privileges. In particular, the following privileges do not have the same effect on an operator's ability to modify a cardholder that they do in the administrative clients:
Disable Card. This privilege has no effect on the 7.90 REST API. You need Edit Cardholders to disable cards.
Add or Edit Cardholder Notes. You also need Edit Cardholders to change notes on an existing cardholder via the API. In the administrative clients, you do not.
Manage Locker Assignments. You also need Edit Cardholders to assign and un-assign lockers on a cardholder via the API. In the administrative clients, you do not.
The 'De-authorise Cardholder' privilege is implemented. It allows an operator to set a
cardholder's authorised field to false (denying all their future access requests) without
the Edit Cardholders privilege.
In short, if your application intends to do more to cardholders than de-authorise them, you will need an operator with Edit Cardholders.
Body
As well as cardholder attributes such as authorised, the PATCH body contains
instructions for creating, updating, and deleting personal data, group
memberships, etc.
Send one of these in a PATCH to /api/cardholders/{id} to modify the cardholder at that
URL, or to add and modify cards, competencies, personal data, group memberships, and
relationships.
This model only shows some of the fields available on a cardholder. The full list is in the cardholder detail.
authorisedbooleanfirstNamestring@employeeIdstringReplace PDF values as though they were flat fields on a cardholder. Prefix the name of
the PDF with @, and Base64-encode images. Send 'null' if you want to delete a PDF value.
Remember that a cardholder's record will not return a PDF if they are not a member of an access group that grants that PDF. Current versions of Command Centre are tolerant of your attempts to change the PDF in that situation, but future versions may not be.
personalDataDefinitionsArray<CardholderPDF>This is how you set and unset the notifications flags on PDFs.
cardsobjectThis object can contain three arrays, named add, update, and remove. Every element
you put in those arrays should be in the card schema.
Each element of the add array will need a type member, at the very least. Every card
field makes sense here except href. Only existing cards have hrefs. This example adds
two cards: one has nothing more than the type, so it will receive blank from and
until dates and a computed number and issue level. The other is a mobile credential (it
has an invitation block) with a custom initial state.
Each element of the update array should be a card to modify. It will need the href of
that card, plus the fields you want to change. Remember you cannot change a card's type.
The example changes the issue level and resets the until date.
The only field that makes sense in an element of the remove array is href and status.
You can remove and add cards in the same PATCH. In fact you should do that in preference
to making multiple API calls. That is a good way of reissuing a mobile credentials, for
example: put the href to the old one in the remove array and a new invitation in the
add array. The new credential should have the same card number and the same type and
invitation blocks as the credential you're re-issuing.
Do not put the same href in both the update and remove arrays.
In version 8.90 and later you can specify a card state in the value field inside the
status block when removing a card or changing its issue level. It becomes the final
state of the card if you remove it or the final state of the card with the previous issue
level if you re-issue it, so it must be one of the valid states for the card type. The
Gallagher clients and the resulting event call this state the 'reason' for the re-issue or
removal. By default it will be the same as the card's current state.
accessGroupsobjectLike the cards object, this can contain three arrays named add, update, and
remove. Every element you send in those arrays should be in the access group
schema.
This operation does not modify access groups in Command Centre; it works on a cardholder's memberships to those groups.
Each element of the add array will need an accessGroup member containing an href
identifying the group to which you wish to add the cardholder. The other fields are
optional. If you omit from, the membership will take effect immediately. If you omit
until, it will be unending.
Each element of the update array will need an href identifying the membership (not the group!) to update,
and one or both of the from and until date-times with new values. You cannot
change the group: just the activity period. The example removes the until date, effectively making
the group membership unending.
In version 7.90.883 or earlier, from and until should be in UTC with a
trailing 'Z'. Releases after 883 understand different timezones here.
Note that updating a cardholder's group membership will change its href, so do not cache it.
The only field that makes sense in an element of the remove array is the href.
Do not put the same href in both the update and remove arrays.
competenciesobjectLike the cards and accessGroups objects, this can contain three arrays named add,
update, and remove. Every element should be in the cardholder competency
schema.
This operation does not modify Command Centre's competencies: it works on a cardholder's holdings of those competencies.
Each element of the add array will need a competency block containing an href member
identifying the competency you wish to grant the cardholder. All other fields are optional.
Note that attempting to give a cardholder a competency he or she already has
will result in an error or an alarm depending on the server version.
Each element of the update array will need an href identifying the
cardholder/competency link to update, and one or more of the expiry, enabled,
enablement, comment, limitedCredit, and credit fields.
The only field that makes sense in an element of the remove array is the href
of the link between the cardholder and the competency.
Do not put the same href in both the update and remove arrays.
In this example we are giving the cardholder a disabled competency which will enable in 2021, and activating another competency.
relationshipsobjectIt should be no surprise that this can contain three arrays named add, update, and
remove, and that every element should be in the relationship
schema.
This operation does not modify Command Centre's roles: it works on the relationships between two cardholders.
Each element of the add array will need a role member containing an href identifying
the type of relationship you wish to establish, and a cardholder member containing an
href identifying the other party (the one who will perform the role for the cardholder at
the URL you are PATCHing). There are no optional fields.
Each element of the update array will need an href identifying the relationship to
update, and one or both of the role and cardholder blocks containing the updated
values. The example leaves the role but changes the cardholder - a new supervisor,
presumably.
The only field that makes sense in an element of the remove array is the href.
Do not put the same href in both the update and remove arrays.
lockersobjectWith you well in the habit by now, each element of your three arrays should be in the cardholder locker schema. They will allocate lockers to cardholders, de-allocate them, and adjust validity periods.
Each member of the add array will need a locker member containing an href identifying
the locker to allocate. The cardholder you will allocate it to is identified by the
request URL, remember.
Each member of the update array will need an href identifying the allocation to update,
and one or both of the from and until date-times.
The validity period is all you can change about a locker allocation. If you want to
change the locker, delete the old one and add a new. You can do that in the same PATCH,
with one element in each of the add and remove arrays.
This example allocates one locker starting in January 2019, sets the end-date of an existing allocation to the end of February 2020, and removes another entirely.
operatorGroupsobjectThis can contain two arrays named add and remove. Every element you send in those
arrays should be in the cardholder operator group
schema.
Each element of the add array will need an operatorGroup member containing an href
identifying the operator group to which you wish to add the cardholder. Supported in 8.50
and later.
The only field that makes sense in an element of the remove array is the href. Make
sure it is the href of the membership, not of the operator group. Supported in 8.90 and
later.
There is nothing about an operator group membership that you can change so there is no
point to an update array. Operator group memberships are, or are not: there is no
update.
elevatorGroupsobjectThis can contain three arrays named add, update, and
remove, each containing an element in the cardholder elevator group
schema.
The elevatorGroup block only makes sense in the add array. The update array is for
changing the cardholder's default floor and passenger types on an existing elevator group
assignment.
The server will ignore all fields in the elevatorGroup and accessZone objects except href
if you place them in the body of your PATCH.
Remove the default floor in an update by supplying an accessZone block with the href set to null or "".
Change a passenger type in an update by supplying the new value. The passenger type will be unchanged otherwise.
As with cards and access group memberships etc., the server ignores root-level hrefs in
the elements of an add array, requires them in the update array (since they indicate
the entries to work on), and ignores everything but them in the remove array.
The example shows our cardholder receiving a default floor for one elevator group and updating the Code Blue and VIP passenger types for another elevator group.
Added in 8.50.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will add feedback from the server about your PATCH.
Success, with no feedback.
The parameters are invalid, or other errors prevented the update.
If you receive 'No fields have been defined for update', check that your submission body is valid JSON.
The site does not have a RESTCardholders licence, or you attempted to set a PDF for which you have no privilege.
The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
A 409 may also mean that the operator does not have the privilege to modify that cardholder, or you attempted to set a field for which you have no privilege (such as 'notes' or 'operatorPassword', both of which require special privileges). The server returns a 409 in these cases for compatibility with some versions of iOS applications. In versions prior to 8.80 your operator needed 'Edit cardholders' to de-authorise a cardholder. In 8.80 and later, 'De-authorise cardholder' on its own is enough.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/cardholders/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/cardholders/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/cardholders/{id}' \
-H 'Content-Type: application/json' \
-d '{
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock'\''s iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"authorised": True,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": True
}
},
{
"@cellphone": {
"notifications": False
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": False,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": True
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": True,
"enableVipFeatures": False
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}
response = requests.patch('https://127.0.0.1:8904/api/cardholders/{id}', json=payload)
data = response.json()This example: - sets `authorised` true, - sets a PDF holding an employee ID, - changes the notification flags on two PDFs (presumably because this cardholder wants to receive notifications at the email address stored in the 'email' PDF rather than by SMS), - adds two new credentials, one card and one mobile, - changes the issue level with `Stolen` as the reissue reason and clears the until date on another credential, - deletes a third credential with `Lost` as the remove reason, - adds two access group memberships, one unending and one with an until date, - clears the until date on another access group membership, - removes a fourth access group membership, - adds a competency, inactive, with a future enablement date, - activates a competency that the cardholder already had, - adds one relationship, - changes the cardholder on another, - adds one locker assignment with a from date, - sets the until date on another locker assignment, - removes a third locker assignment, - adds two operator group memberships, - removes a third operator group membership, - adds an elevator group with a default floor, - modifies another elevator group, turning the 'code blue' feature on and the VIP feature off, and - removes a third elevator group.
{
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}This is a minimal object for giving a cardholder a new card, containing nothing more than the card type. See the [card detail](#definition-CardholderCard) for other fields you can (and probably should) use, such as the card's issue level and from/until dates. `type` is the only compulsory field in a new card. It must contain an href to the card type of the new card. We also set this cardholder's first name, just for the sake of illustration.
{
"firstName": "Nick",
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
}
}
]
}
}This is an example of what you would send to add two mobile credentials to an existing cardholder. `type` is the only compulsory field. It must contain an href to the card type of the new mobile credential. The so-called card "number" on a mobile credential does not need to be numeric. The `status` block is optional. Provide a field `value` inside it set to one of the valid starting states for the mobile credential. If you omit it, the credential will start in the default state. `invitation` is a block containing fields that describe how Command Centre should set about registering the mobile device. If you specify `mobile` you must also supply `email`. If you give neither, Command Centre will not send an invitation to the cardholder. `singleFactorOnly` defaults to false, which is the recommended setting. See the [card detail](#definition-CardholderCard) for more.
{
"firstName": "Nick",
"cards": {
"add": [
{
"number": "Nick's mobile",
"status": {
"value": "active"
},
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2018-01-01T00:00:00.000Z",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789",
"singleFactorOnly": true
}
},
{
"number": "Nick's other mobile",
"status": {
"value": "active"
},
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"invitation": {
"email": "nick@example.com",
"mobile": "02198765432",
"singleFactorOnly": true
}
}
]
}
}Remove an access group membership
This call removes a cardholder's membership in an access group. Note that a cardholder may have more than one membership in a group.
You can find this URL in the cardholder object. Do not build it yourself.
To modify a cardholder's membership of an access group your operator must have a privilege that allows editing that cardholder plus 'Modify access control' on the group.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
The operator does not have sufficient privilege.
The operator does not have sufficient privilege.
That is not the href of an access group membership.
The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
A 409 may also mean that the operator does not have the privilege to modify that cardholder, or you attempted to set a field for which you have no privilege (such as 'notes' or 'operatorPassword', both of which require special privileges). The server returns a 409 in these cases for compatibility with some versions of iOS applications. In versions prior to 8.80 your operator needed 'Edit cardholders' to de-authorise a cardholder. In 8.80 and later, 'De-authorise cardholder' on its own is enough.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}')
data = response.json()Remove a card from a cardholder
This call removes a card from a cardholder.
You can find this URL in the cardholder object. Do not build it yourself.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
Success.
The operator does not have a privilege that allows editing that cardholder.
That card is not on that cardholder.
The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
A 409 may also mean that the operator does not have the privilege to modify that cardholder, or you attempted to set a field for which you have no privilege (such as 'notes' or 'operatorPassword', both of which require special privileges). The server returns a 409 in these cases for compatibility with some versions of iOS applications. In versions prior to 8.80 your operator needed 'Edit cardholders' to de-authorise a cardholder. In 8.80 and later, 'De-authorise cardholder' on its own is enough.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}')
data = response.json()Remove a competency from a cardholder
This call removes a competency from a cardholder.
You can find this URL in the cardholder object. Do not build it yourself.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
Success.
The operator does not have a privilege that allows editing that cardholder.
That is not the href of a cardholder's competency.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}')
data = response.json()Change competency credit
This call increases or decreases a cardholder's competency credit. It is an indivisible operation.
It is reserved for the Pre-pay Car Parking feature.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
addintegerrequiredqueryThe amount to adjust the competency credit. This can be positive or negative.
Response
Success. Future versions will return feedback from the server.
Success.
The operator does not have a privilege that allows editing that cardholder.
The parameters are invalid.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/credit", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/credit"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/credit'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/credit', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/credit', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/credit')
data = response.json()Remove an elevator group from a cardholder
This call removes a default floor assignment and passenger types from a cardholder for one elevator group.
You will find this URL in the elevatorGroups block of a cardholder
object. Do not build it
yourself.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
Your operator does not have the necessary privilege to change this cardholder's elevator groups.
That is not the URL of a cardholder's elevator group. You can take an href
from the elevatorGroups block of a cardholder detail. The message in the results
document will tell you more about the problem.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}')
data = response.json()Remove a locker assignment
This call removes a locker assignment from a cardholder. If the cardholder has no other assignments for this locker after this operation he or she will not be able to open it.
You will find this URL in the lockers block of a cardholder
object. In the interest of forward compability, do not
build it yourself.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
You do not have privileges for the operation.
You do not have privileges for the operation.
That is not the URL of a cardholder's locker assignment. You can take an href from the 'lockers' block of a cardholder detail, or the 'assignments' block of a locker or locker bank detail. The message in the results document will tell you more about the problem.
You do not have privileges for the operation.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}')
data = response.json()Remove an operator group membership
This call removes a cardholder's membership in an operator group. Operator group memberships do not have start and end dates, so a cardholder can only have one membership in a given operator group.
You can find this URL in the cardholder object. In the interest of forward compability, do not build it yourself.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
The operator does not have a privilege that allows editing that cardholder's operator group memberships ('Modify operator group membership').
That is not the href of an operator group membership.
The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
A 409 may also mean that the operator does not have the privilege to modify that cardholder, or you attempted to set a field for which you have no privilege (such as 'notes' or 'operatorPassword', both of which require special privileges). The server returns a 409 in these cases for compatibility with some versions of iOS applications. In versions prior to 8.80 your operator needed 'Edit cardholders' to de-authorise a cardholder. In 8.80 and later, 'De-authorise cardholder' on its own is enough.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}')
data = response.json()Remove a relationship
This call severs a relationship between two cardholders.
You can find the URL in the relationships block in the cardholder
object of the cardholder who has the relationship, not the
cardholder who holds the role. For example if you have a 'supervisor' role you would find
the URL to delete by looking up the supervised cardholder, not the supervisor.
In the interest of forward compability, do not build the URL yourself.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success.
The operator does not have a privilege that allows editing that cardholder.
That is not the URL of a relationship. Perhaps it is deleted already.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}')
data = response.json()Change a cardholder's location
This call updates a cardholder's location (moves them) to a target access zone. Added in 8.20.
Do not code this URL into your application. Take
it from the updateLocation.href field in a cardholder
response.
The POST expects a document which contains an href to the target access zone. The recommended way of getting access zone hrefs is through an access zones call added in 8.20 that returns you the access zones to which you are allowed to move cardholders, according to your operator privileges.
You can also get access zone hrefs from the items controller.
It is not possible to put a cardholder into "nowhere", also known as "outside the system". The only place you can move them is a new access zone.
Unlike cardholder PATCHes, this operation is not blocked by another operator holding a write lock on the cardholder.
Note that unless you are adding a historical movement event (see below) your REST operator will need the "Manage Cardholder Location" privilege in the division of the target access zone and "View Cardholder" on the cardholder itself.
Adding historical movements
It is possible to add a movement event dated in the past, making it look as though the cardholder moved at some point in history. For this to work:
your server must have the Regulated Zones customisation licence, and
the time must be in the past, more recent that the cardholder's most recent movement, and more recent than the "Maximum age of historical manual access events" setting in the "Operator defaults" tab of the server properties, and
your operator must have the 'Manage historical cardholder location' privilege in the division of both the cardholder and the target access zone.
That privilege is not included in 'Advanced user', by the way. Not that your API operators would ever have 'Advanced'.
Added in 9.40.
Body
The body of the request must contain the href of the target access zone.
accessZoneobjecttimestring<date-time>First available in 9.40, this changes the request from a real-time move into an historical one. It needs a different licence and privilege, covered in this route's description. Also, this field's value must be in the past, more recent than the cardholder's most recent movement, and more recent than the "Maximum age of historical manual access events" server property.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server.
Success.
The cardholder ID or access zone href is invalid.
The operator does not have a privilege ('Manage Cardholder Location') that allows moving this cardholder to the target zone.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
},
"time": "2026-05-12T12:00:00.000Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/{id}/update_location", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
},
"time": "2026-05-12T12:00:00.000Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/{id}/update_location") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/{id}/update_location' \
-H 'Content-Type: application/json' \
-d '{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
},
"time": "2026-05-12T12:00:00.000Z"
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/update_location', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
},
"time": "2026-05-12T12:00:00.000Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/update_location', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
},
"time": "2026-05-12T12:00:00.000Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
},
"time": "2026-05-12T12:00:00.000Z"
}
response = requests.post('https://127.0.0.1:8904/api/cardholders/{id}/update_location', json=payload)
data = response.json(){
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412"
}
}{
"accessZone": {
"href": "https://host.com:8904/api/items/412"
}
}{
"accessZone": {
"href": "https://host.com:8904/api/items/412"
},
"time": "2026-05-08T10:00:00Z"
}Find out which fields you can edit
This tells you which fields your REST operator can edit based on its privileges.
Gallagher uses this call for rapidly-evolving internal applications. As such, its results are tuned to those applications and are subject to change. Rather than relying on this call, we suggest that you simply give your REST operator the privileges it needs to access to everything it needs.
Note that this method returns 400 if there are two PDFs in the system with the same name. CC insists on unique names for items when you create them but you can end up with duplicates when you form a multiserver cluster out of standalone installations. You should move on that, because having two PDFs with the same name will bewilder your operational staff.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
You do not have privileges for the operation.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/cardholders/{id}/edit", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/cardholders/{id}/edit"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/cardholders/{id}/edit'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/edit', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id}/edit', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/cardholders/{id}/edit')
data = response.json()Cardholder changes
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
First, get a bookmark at the end of Command Centre's list of changes as follows:
1a.
GET /api1b. Follow the link at
features.cardholders.changes↪.1c. Take note of the href in the
nextblock. This is your bookmark. Later on you will use it to ask for all the changes that happen after this point.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 /apiagain, follow the link atfeatures.cardholders.cardholders↪, adding query parameterstop=1000andsort=id.2b. Process those cardholders (probably 1000) and follow the
next.hreflink in a loop until you have extracted all of them.This is the start of your update loop. The first time through, to catch the changes that came in during the big download you did in step two, use the href that you got in step one. Simply GET it.
If the
resultsarray 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 fromnext.href.If the
resultsarray is not empty it will contain the first changes that happened after your previous call. The sections below will help you process them.Restart your polling loop using the link in
next.href.
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, discard all 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 to 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
lastSuccessfulAccessbut 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.
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.
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 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
filterparameter. If you are only interested in people's access group memberships, for example, addfilter=accessGroupsto your GET, and you will not be troubled with all the other kinds of changes that cardholders go through.The default set of fields is large and expensive to compute. Ease the load on the server, the network, and your client by asking for a smaller response. For example if you are only interested in the current state of a cardholder's group memberships and do not care who made the change, or when, or what state the cardholder was in before, use
fields=item,cardholder.accessGroups. You needitemso you can tell which cardholder changed. If you store your own identifier in a PDF you could dropitemand addcardholder.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 REST operator access to those divisions. It will not see changes outside them.
Parameters
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
filterArray<string>hrefidfirstNamelastNameshortNamedescriptionauthorisedlastSuccessfulAccessZonedivisionnotespersonalDataFieldsoperatorLoginEnabledoperatorUsernameoperatorPasswordoperatorPasswordExpiredwindowsLoginEnabledwindowsUsernamecardsaccessGroupscompetenciesnotificationsrelationshipslockersdefaultsdefaultsqueryLimits the search results to the changes that affected these fields, and limits the
oldValues and newValues blocks to these fields. You can specify practically any of
the fields in the cardholder detail. You can also go
into more detail; for example you can monitor a cardholder's cards' validity dates using
filter=cards.from,cards.until.
filter reduces the number of results; if you want to choose the blocks you receive in
each result, use fields.
If you do not supply a filter parameter it will use the same fields you get in a
cardholder details page. That is nearly everything the API has for a cardholder, but
omits some seldom-used or expensive features such as card tracing and the large PIV
fields. If you want to monitor them you must list them here. For example,
filter=defaults,cards.trace will add the card trace flag to the usual filter.
personalDataFields will filter for PDF changes, and will give you the
personalDataDefinitions block plus the PDF values that changed. You cannot filter for
changes to a particular PDF: you will need to do that in your client.
Being able to monitor operator settings arrived in 8.50 and operator group memberships in 8.60.
Note that 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.
If card changes are all you are interested in, and you do not want to hear about all the
other changes that cardholders can experience, use filter=cards.
fieldsArray<string>hrefoperatoroperator.hrefoperator.nametimetypeitemoldValuesnewValuescardholdercardholder.*defaultsdefaultsqueryLimits the blocks in the results.
fields affects the blocks in each result; if you want to reduce the number of results,
use filter.
You can have finer-grained control of fields inside those blocks by listing their JSON
paths. For example, to see what the operator and affected cardholder's names are now, you
could use fields=operator.name,cardholder.firstName,cardholder.lastName.
The values you can list for the cardholder block are very similar to these field
names. Prefix each with cardholder. (since in this
API they are all inside a block called cardholder).
Separate values with commas and treat the strings as case sensitive.
If you do not send this parameter the API will return all cardholder changes to a default set of fields.
Use personalDataFields for changes to PDF values.
deadlineinteger>= 050querySets the number of seconds after which the server will abort the query and return a 500. If that happens, you should try again later when the server (particularly the database server) is not so busy.
Using this can be dangerous. When the server aborts a query it discards all the work it did up to that point. If you send the same query again later, with the same deadline, you may end up stuck in a loop, never making progress, and effectively DoSing your server.
If your server is struggling, try reducing the size of each result set using top.
Added in 8.80.
Response
Success.
The site does not have the RESTCardholders licence.
The server was too busy to complete the request before the deadline.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/cardholders/changes", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/cardholders/changes"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/cardholders/changes'const response = await fetch('https://127.0.0.1:8904/api/cardholders/changes', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/changes', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/cardholders/changes')
data = response.json(){
"results": [
{
"href": "https://host.com:8904/api/cardholders/changes/f4e67a",
"time": "2020-01-14T03:14:33Z",
"type": "update",
"item": {
"href": "https://host.com:8904/api/cardholders/525"
},
"operator": {
"name": "System Operator",
"href": "https://host.com:8904/api/items/1"
},
"oldValues": {
"firstName": "Craig",
"competencies": [
{
"enablement": "",
"href": "https://host.com:8904/api/cardholders/525/competencies/3910e4"
}
]
},
"newValues": {
"firstName": "Gavin",
"competencies": [
{
"enablement": "2020-02-29T00:00:00Z",
"href": "https://host.com:8904/api/cardholders/525/competencies/3910e4"
}
],
"cards": [
{
"number": "2",
"cardSerialNumber": "",
"issueLevel": 1,
"from": "",
"until": "",
"href": "https://host.com:8904/api/cardholders/525/cards/285f779af1ef49abbba"
}
],
"accessGroups": [
{
"accessGroup": {
"name": "Access Group 1",
"href": "https://host.com:8904/api/access_groups/499"
},
"from": "",
"until": "2020-01-15T04:39:00Z",
"href": "https://host.com:8904/api/cardholders/525/access_groups/f9cb328b4"
}
]
},
"cardholder": {
"firstName": "Gavin",
"cards": [
{
"number": "2",
"cardSerialNumber": "",
"issueLevel": 1,
"from": "",
"until": "",
"href": "https://host.com:8904/api/cardholders/525/cards/285f779af1ef49abbba"
}
]
}
}
],
"next": {
"href": "https://host.com:8904/api/cardholders/changes?pos=SWEp9"
}
}Card Types
The main purpose of the card type API is to find the card type you need when assigning a card to a cardholder.
A card connects a cardholder to a card type. A new card takes some defaults from its card type (such as its activity period, given by 'from' and 'until'), some limits (on the card number and PIN, for example), and other behaviours (such as how long a card can be inactive before Command Centre disables it).
The 'Site Installation' chapter of the Configuration Client online help has a section that describes card types.
As it does for the other item types, the REST API gives you read access to card types through
search and details pages. However the privilege model for card types is more flexible than for
other items: the 'View site' privilege determines whether you are allowed to view a card type,
while the cardholder editing privileges ('Create', 'Edit', 'Create and edit') determine whether
you can assign cards of that type to a cardholder, so version 8.10 of the API added a page that
returns the card types that your operator is allowed to use in that way. Take its URL from
features.cardTypes.assign in the results of GET /api.
This API uses the term 'card' but more broadly we prefer 'credential', because not all card types involve a physical card.
The API's coverage of PIV cards is in its own document.
API routes that allow creating, modifying, and deleting card types are in development.
Use cases
Finding the PIV card type
When you give a cardholder a card, PIV or otherwise, you need to provide the identifier of the card type. It will vary between Command Centre installations so you cannot use a value from another installation or from these examples. It will not change while Command Centre is running but it may change at upgrade, so your application should follow this process at startup.
It takes two queries and a loop:
GET /api.- If running 8.00 or earlier, follow the link at
features.cardTypes.cardTypes.href(which will be to/api/card_types), or - if running 8.10 or later, follow the link at
features.cardTypes.assign.href(which will probably be to/api/card_types/assign. Both URLs will work in 8.10, but the advantage of this URL is that your operator can access it at a lower privilege level). - Iterate through the array to find the element with
credentialClass: piv, and - note its
href.
You can accomplish the last two steps with the JSONPath filter
$.results[?(@.credentialClass=='piv')].href.
Explanation: Command Centre ships with a handful of card types, and administrators can add more, but the one that Command Centre uses for PIV and PIV-I cards has its own credential class.
Licensing
Reading card types is enabled by the RESTCardholders licence but creating, deleting, or modifying them requires the RESTConfiguration licence.
Search card types
This returns the card types your operator is privileged to view. They may be different card types from those you are allowed to assign to cardholders, and since that is the only thing that this API does with card types, you should probably be using that function instead.
Response
Success.
The installation lacks a cardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/card_types", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/card_types"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/card_types'const response = await fetch('https://127.0.0.1:8904/api/card_types', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/card_types', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/card_types')
data = response.json()Create a card type [coming]
Creates a new card type, setting practically everything except the PIV fields.
Do not code this URL into your application. Take
it from the href field in the features.cardTypes.cardTypes.href field of GET /api.
The POST expects a document in the same format as the card type detail.
When successful it returns a location header containing the address of the new card type.
Note that you can only create one item per POST.
This call requires the RESTConfiguration licence.
Body
Note that not all the fields in this schema make sense to send when creating a new card
type. id and href, for example.
This object describes a single Card Type. "Credential type" would be a better name, as Command Centre's card types include credentials that do not require a plastic card.
hrefstring<uri-reference>read onlyidstringread onlyThe API does not use this field. Nor should you.
namestringdivisionobjectThe division that contains this card type. Required when creating a card type.
notesstringFree text.
Because of its potential size, the server does not return the notes field by default. You
need to ask for it with fields=notes.
facilityCodestringA facility code is a letter (A-P) followed by up to five digits. It is encoded onto cards so that they only work at sites with the correct facility code.
PIV cards, PIV-I cards, and mobile credentials do not have a facility code.
Most credential classes require a facility code on creation. But it cannot be changed once set, so this field is ignored in a PATCH.
availableCardStatesArray<string>ActiveDisabled (manually)LostStolenDamagedread onlyAll credential types have a set of card states.
If you need this, ask for it using the fields parameter.
This field is read-only: it is derived from the card type's card state set (also known as a workflow). If you send it in a POST or a PATCH, the server will ignore it.
credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransactRequired when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.
minimumNumberstringFor card types with integer card numbers, this is the minimum. Must be non-negative.
maximumNumberstringFor card types with integer card numbers, this is the maximum. Must be non-negative.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
regexstringThis is the regular expression that a text card number must match before Command Centre will accept it.
regexDescriptionstringRegular expressions often need explaining to your users.
cardStateSetanyDocumentation coming. TODO
POST only.
defaultIssueLevelanyDocumentation coming. TODO
cardIssueLevelMustMatchDefaultanyDocumentation coming. TODO
enableExpiryNotificationsanyDocumentation coming. TODO
warningPeriodanyDocumentation coming. TODO
pinLengthanyDocumentation coming. TODO
inactivityTimeoutInDaysanyDocumentation coming. TODO
allowReprintanyDocumentation coming. TODO
allowReEncodeanyDocumentation coming. TODO
isEngageanyDocumentation coming. TODO
sendRegistrationEmailanyDocumentation coming. TODO
sendRegistrationSmsanyDocumentation coming. TODO
warningPeriodTypeanyDocumentation coming. TODO
Days, Weeks, Months, or Years.
cardNumberFormatanyDocumentation coming. TODO
POST only.
defaultExpiryTypeanyDocumentation coming. TODO
ExplicitDateTime or Duration.
defaultExpiryDurationanyDocumentation coming. TODO
(if the type is duration)
defaultExpiryFromanyDocumentation coming. TODO
defaultExpiryUntilanyDocumentation coming. TODO
(if defaultExpiryType is explicitDateTime)
engageCardFormatanyDocumentation coming. TODO
Only if isEngage is true. There are nine acceptable values.
appleTemplateIdanyDocumentation coming. TODO
Required if credential class is Apple Pass.
appleLinkedCredentials |anyDocumentation coming. TODO
The POST format is different from the PATCH format's add and remove arrays.
Response
Success.
The body of the POST did not describe a valid card type.
If you see 'Invalid Access Group JSON object', the server could not parse the JSON in the body of your POST. Remember to quote all strings, especially those than contain @ symbols.
The operator does not have a privilege that allows creating card types, or the server does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/card_types", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/card_types") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/card_types' \
-H 'Content-Type: application/json' \
-d '{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}'const response = await fetch('https://127.0.0.1:8904/api/card_types', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/card_types', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}
response = requests.post('https://127.0.0.1:8904/api/card_types', json=payload)
data = response.json(){
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}Search usable card types
This returns the card types you are privileged to assign to a cardholder.
Since a site usually has only a few card types, and each is small, the search, sorting, field selection, and pagination parameters are probably of little use to you. But they work.
If your result set is empty it means your operator does not have the privilege to view any card types. Your operator needs a privilege that allows assigning cards and credentials to cardholders such as 'Add / Edit Card or Credential' or 'Create / Edit / Create and Edit Cardholders'.
You can find this URL in features.cardTypes.assignin /api. It was new in 8.10. In the
interest of forward compability, do not build it
yourself.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnamedivisionfacilityCodeavailableCardStatescredentialClassminimumNumbermaximumNumbernotesregexregexDescriptionquerySpecifies which fields to return instead of the default set (which contains nearly every field). Separate values with commas.
Treat the string matches as case sensitive: use 'facilityCode' rather than 'facilitycode'.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success.
The installation lacks a cardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/card_types/assign", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/card_types/assign"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/card_types/assign'const response = await fetch('https://127.0.0.1:8904/api/card_types/assign', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/card_types/assign', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/card_types/assign')
data = response.json(){
"results": [
{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}
],
"next": {
"href": "https://host.com:8904/api/card_types/assign?skip=1000"
}
}Get one card type
This returns some basic data for a card type. It exists so that clients following card type hrefs from other controllers do not receive a 404. To find out about card types available for assigning to cardholders, use card_types/assign instead.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
The installation lacks a cardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/card_types/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/card_types/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/card_types/{id}'const response = await fetch('https://127.0.0.1:8904/api/card_types/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/card_types/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/card_types/{id}')
data = response.json()Remove a card type [coming]
This call removes a card type from Command Centre. In the interest of forward compability, take the URL from the results of a card type search rather than building it yourself.
This call requires the RESTConfiguration licence.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Success.
Deleting the card type failed. This happens when the card type is in use. Most often it is because a cardholder still has a card of this type, but card types can also be connected to other items.
The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.
That is not the URL of a card type, or the operator is not privileged to view it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/card_types/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/card_types/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/card_types/{id}'const response = await fetch('https://127.0.0.1:8904/api/card_types/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/card_types/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/card_types/{id}')
data = response.json()Update a card type [coming]
This is the call you use to update a card type. Take the URL from the results of a card type search. In the interest of forward compability, do not build it yourself.
The PATCH expects a document in the same format as the the card type detail but with fewer fields.
This call requires the RESTConfiguration licence.
Body
Note that not all the fields in this schema make sense to send when creating a new card
type. id and href, for example.
This object describes a single Card Type. "Credential type" would be a better name, as Command Centre's card types include credentials that do not require a plastic card.
hrefstring<uri-reference>read onlyidstringread onlyThe API does not use this field. Nor should you.
namestringdivisionobjectThe division that contains this card type. Required when creating a card type.
notesstringFree text.
Because of its potential size, the server does not return the notes field by default. You
need to ask for it with fields=notes.
facilityCodestringA facility code is a letter (A-P) followed by up to five digits. It is encoded onto cards so that they only work at sites with the correct facility code.
PIV cards, PIV-I cards, and mobile credentials do not have a facility code.
Most credential classes require a facility code on creation. But it cannot be changed once set, so this field is ignored in a PATCH.
availableCardStatesArray<string>ActiveDisabled (manually)LostStolenDamagedread onlyAll credential types have a set of card states.
If you need this, ask for it using the fields parameter.
This field is read-only: it is derived from the card type's card state set (also known as a workflow). If you send it in a POST or a PATCH, the server will ignore it.
credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransactRequired when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.
minimumNumberstringFor card types with integer card numbers, this is the minimum. Must be non-negative.
maximumNumberstringFor card types with integer card numbers, this is the maximum. Must be non-negative.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
regexstringThis is the regular expression that a text card number must match before Command Centre will accept it.
regexDescriptionstringRegular expressions often need explaining to your users.
cardStateSetanyDocumentation coming. TODO
POST only.
defaultIssueLevelanyDocumentation coming. TODO
cardIssueLevelMustMatchDefaultanyDocumentation coming. TODO
enableExpiryNotificationsanyDocumentation coming. TODO
warningPeriodanyDocumentation coming. TODO
pinLengthanyDocumentation coming. TODO
inactivityTimeoutInDaysanyDocumentation coming. TODO
allowReprintanyDocumentation coming. TODO
allowReEncodeanyDocumentation coming. TODO
isEngageanyDocumentation coming. TODO
sendRegistrationEmailanyDocumentation coming. TODO
sendRegistrationSmsanyDocumentation coming. TODO
warningPeriodTypeanyDocumentation coming. TODO
Days, Weeks, Months, or Years.
cardNumberFormatanyDocumentation coming. TODO
POST only.
defaultExpiryTypeanyDocumentation coming. TODO
ExplicitDateTime or Duration.
defaultExpiryDurationanyDocumentation coming. TODO
(if the type is duration)
defaultExpiryFromanyDocumentation coming. TODO
defaultExpiryUntilanyDocumentation coming. TODO
(if defaultExpiryType is explicitDateTime)
engageCardFormatanyDocumentation coming. TODO
Only if isEngage is true. There are nine acceptable values.
appleTemplateIdanyDocumentation coming. TODO
Required if credential class is Apple Pass.
appleLinkedCredentials |anyDocumentation coming. TODO
The POST format is different from the PATCH format's add and remove arrays.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid card type. Card types have many rules, easily enforced in a graphical interface but less so in an API. See the body of the response for help on what went wrong.
The operator has a privilege that allows viewing the item but not modifying it, or you tried to set the division to one you cannot configure, or the server is missing the necessary licence.
You need the 'Configure Site' privilege on the item you are changing, which means you need on it on the item's current division. You also need it on the new division, if you are changing that.
This is also the response when the server does not have the 'RESTConfiguration' licence.
That is not the URL of a card type or your operator does not have the privilege to view it. This probably means you have built the URL yourself instead of taking it from the results of a GET.
The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/card_types/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/card_types/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/card_types/{id}' \
-H 'Content-Type: application/json' \
-d '{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}'const response = await fetch('https://127.0.0.1:8904/api/card_types/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/card_types/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}
response = requests.patch('https://127.0.0.1:8904/api/card_types/{id}', json=payload)
data = response.json(){
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}Changelog
API changes on the roadmap
Alarms will have a block containing their event group, as events do.
You will be able to set the number of alarms you want coming back from a GET, as you can with events.
Potentially breaking change: the API routes that return the value of a cardholder's date PDF present it in either in RFC 3339 format with a time, or a format specific to the server's locale, depending on the route. A future version will change to date-only ISO 8601 (date PDFs only contain a date, not a time).
Potentially breaking change: the API route that returns the value of a string PDF will change to return a 404 if the cardholder does not have a value for it, as it does for image and date PDFs. Currently, missing string PDFs come out as a zero-length payload.
Events indicating that an operator modified an item may not have a cardholder at the end of the
operatorhref. It may be a new kind of item, because we have new events coming that will indicate when an integration modifies an item rather than a human operator.You will be able to create, delete, and change the the basic configuration of roles, PDFs, and card types.
POTENTIALLY BREAKING CHANGE: searching for cardholders using
pdf_xxx=""orpdf_xxx=with no search value currently returns no cardholders. That is neither useful nor desired. Instead, a future version of Command Centre will return cardholders with a blank value for that PDF.POTENTIALLY BREAKING CHANGE: the API route that lets you modify a cardholder by PATCH also works if you send it a POST. It should not, because POSTs create things, not modify them. The fix will break clients that rely on this. There should be no clients relying on it since the POST was not documented.
POTENTIALLY BREAKING CHANGE: in future, PATCH methods will return "200 Success" instead of "204 No Content" and may contain the object you modified plus a message from the API giving you feedback on your request. Please be aware that all 200-level response codes mean success, not just the ones old versions have been sending you.
A cardholder will show its visits and escort.
PDFs will have "Revealable" access levels.
Current versions of the API only show a PDF on a cardholder if he or she has a value for that PDF. That makes it difficult to find out which PDFs a cardholder could have, so a future version will allow you to ask for all of them with
fields=allPdfs.You will be able to request the competencies that your operator has the permission to assign. That is determined by privileges, a competency setting, and an operator group setting. Current versions let you request the competencies that your operator has the permission to view, which is a larger set determined by privileges alone, and not suitable for interactive REST clients.
You will be able to create and delete alarm zones, and modify some basic fields.
API changes in 9.60
You can move cardholders outside the system.
The items controller can give you items' descriptions and short names, if you ask.
API changes in 9.50
OIDC login support for operators.
Creating an item status subscription and collecting the first batch of changes will no longer return the first set of results twice. If your application follows the algorithm at the second link above it will always be compatible with this API.
The performance improvements were such that we felt it worthwhile to port it back to all supported versions, so it is also present in the latest maintenance releases of 9.10 through 9.40.
Potential breaking change: the maximum length of an item's name increased from 60 to 110 characters, and attempts to set a name longer than that will fail. Earlier versions silently truncated the name and let the update proceed. Cardholder first and last names remain capped at 50 characters each.
The Apple Employee Badge Type and State Set now have proper canonical type names, improved from
type_279andtype_280.Cardholders have accessibilities for skipping PIN entry and taking longer to get through a door, in addition to the existing accessibility to not use a scramble pad.
POTENTIALLY BREAKING CHANGE: GovPass cards and card states may have changed item type names between 9.20 and 9.30, depending on the card type and the site's licences.
API change in May 2026 maintenance releases
- Potential breaking change in 9.20 MR7, 9.30 MR5, and 9.40 MR2: attempts to set the name of an item that is not a cardholder to a string that is longer than 60 characters will fail. Earlier versions silently truncated the name and let the update proceed. There is no change to the handling of cardholders' names.
API changes in 9.40
Support for half-cycle door locks. Common in correctional facilities, half-cycle locks remain unlocked until closed. Regular doors unlock for a few seconds before locking again.
POTENTIALLY BREAKING CHANGE: for efficiency, 9.30 orders search results differently from 9.20 when your query does not include a
sortparameter. If you care about the order of your items, tell the server how you want them sorted.POTENTIALLY BREAKING CHANGE: GovPass cards may have a new credentialClass and GovPass card states will have new names, depending on the card type and the site's licences.
You can create, modify, and delete access groups.
API changes in 9.30
You can create, modify, and delete divisions and access groups.
The server will accept TLS 1.3 connections when running on suitable versions of Windows. Prior versions would only accept TLS 1.2 connections.
An API client can switch its own status between
normalandfaulty, and can set arbitrary status text. Marking the item as faulty will raise an alarm in Command Centre.Command Centre can, optionally, raise an alarm if an API client has not made an API call within a configurable period. This is an addition to Command Centre, not to the API, but deserves a mention here for developers considering adding status monitoring to their integrations. It may be done for you!
API changes in 9.20
- You can filter events and items by their direct division. The older division search is recursive: it considers everything that has any of the filter's divisions anywhere in its ancestry. The new division search is shallow: it only returns events and items that are direct members of the division in the filter.
API changes in 9.10
You can request the name of an alarm's instruction, and the contents of the instruction itself.
You can create, modify, and delete competencies.
You can request a locker's status and send an override to quarantine it. The use cases section shows how.
API changes in 9.00
A new event type
hasLocationgreatly simplifies the monitoring of cardholder movementsA new (
locationblock on events greatly simplifies interpreting them.You can search for cardholders by the access zone they are in.
GovPass cards have two new fields,
visitorContractorandownedBySite.
Alarms and events API changes in 8.90
BREAKING CHANGE: the operator must have the 'Create Events and Alarms' privilege in the division of the source item, if your request specifies a source item. Current versions only require that the operator has that privilege on at least one division.
To improve the symmetry between events and alarms, they both now carry a field
eventTypecontaining the ID and name of their type. Thetypefield, which has different contents for alarms and events, is now obsolete (but still returned in results).Alarms contain a new field called
eventcontaining a link to the corresponding event, mirroring the existingalarmfield coming the other way from an event.You can request a division's description. This has been possible since 8.50 but was not certified until 2022, so it gets a mention now.
Write-only access to card PINs.
You can set a reason when re-issuing or removing a card which will appear on the resulting event. The default is the card's previous state.
You can now set a visitor's state (signing in, etc.). Visits will return that, if you ask.
Bug fix: removing an operator group from a cardholder by PATCH was not possible in the early versions of 8.70 and 8.80. It was fixed in 8.70.2113, 8.80.1116, and all versions of 8.90.
API changes in 8.80
BREAKING CHANGE: Date PDFs no longer come out of the API with a time component or time zone designator, because date PDFs don't hold a time or time zone. Previously they came out as midnight UTC, which implied more accuracy than date PDFs hold.
An optional PDF field
operatorAccesscontains the level of access your operator has to cardholders' values for the PDF.Card type
regexandregexDescriptionfields.Email and mobile PDFs return their 'Default Notifications' flag on request. This actually happened in 8.50 but did not make it into the "changes" list.
Early-adopter support for Interlock groups.
API changes in 8.70
Each event in a page of search results will contain URLs to continue the search after that event. This is a significant benefit to integrations that extract large pages of events and may encounter a problem mid-page, and have to resume without loss or duplication later.
A new privilege 'View Cardholder Events' grants visibility of an event if the operator has the privilege in both the event's division and the event's cardholder's division. This allows a site to create a REST client that can monitor selected cardholders' movements through selected areas, but cannot see any other activity in those areas.
You may now use the
relatedItemquery parameter in an item search to find events that are associated with a particular item.BREAKING CHANGE: Missing image PDFs will no longer have "not captured" as their value. Instead, they will have no value at all.
A cardholder's operator group memberships (added in 8.50) now contain an href that you can use in an HTTP DELETE as another way of removing a cardholder from an operator group.
The server returns 1000 items by default instead of 100.
Image PDFs tell you whether they are the cardholder's profile picture.
Image PDFs have a new optional field
contentTypethat returns their MIME type. This deprecates the oldimageFormatfield, which is non-standard.Asking for a particular PDF on a cardholder (by putting
pdf_XXXinto thefieldsquery parameter) will cause it to appear even if the cardholder has no value for that PDF. Previous versions only showed if it had a non-blank value.Attempting to give a cardholder a competency they already have will fail instead of allowing it and raising an alarm. Note that a cardholder having two links to the same competency leads to undefined behaviour.
A new query parameter
requested_bylets you attribute overrides to another cardholder. Your operator must have the 'Delegate API Activity' privilege in the cardholder's division.Item updates now include fields that changed to an empty value. Versions up to 8.60 returned an update but did not tell you which field changed or what it changed to, if it changed to a blank.
API changes in 8.60
Card numbers on Bluetooth (mobile) and PIV card events are now full-length. Mobile numbers are now the phone's ID string, changed from the number that 8.50 returned. Decimal numbers now come out unsigned. Note that neither 8.50's numbers nor 8.60's ID strings are guaranteed unique across phones.
Access events that do not have a card, such as someone entering their user code instead of badging their card, will no longer return a card block.
You can search for cardholders by division or description.
8.60 rejects unsupported HTTP verbs instead of treating them like a GET. For example, if you send a POST to
/api/competencies8.50 will return a list of competencies but 8.60 will return a 400-level error.In build 8.60.1684 or later, attempting to give a cardholder a competency they already have will fail instead of allowing it and raising an alarm. Note that a cardholder having two links to the same competency leads to undefined behaviour.
The RESTOverrides licence allows you to find items and their override URLs.
API changes in 8.50
The server property that turns off client certificate checking changed.
Divisions can return some of their Visitor Management configuration.
You can view any item's notes. You have been able to for a while now, but it was missing from this document.
You can request a division's description.
You can manage operators by setting their operator group memberships and logon credentials.
You can view operator groups.
Lockers and locker banks report the hardware controller where they reside.
Cardholders report their 'disable cipher pad' setting (vision impairment).
Card types will show their division on request.
Cardholders can have default floors and passenger type flags per elevator group. Thyssen-Krupp systems can prepare an elevator car when Command Centre grants a cardholder access through lobby turnstiles.
You can list receptions and see enough of a division's visitor management configuration to create and manage visits.
Image Personal Data Field definitions will show their width, height, and format (JPG, PNG, or BMP) on request.
Email and mobile PDFs return their 'Default Notifications' flag on request.
A schedules API provides CRUD of the six schedule item types.
A day categories API returns the day categories you need for those schedules.
A pulse overrides an output.
A 'connectedController' field grew on the item types that did not already have it.
An elevator groups API provides a view of elevator groups so that you can set a cardholder's default floor.
API changes in 8.40
You may now use the
fieldsquery parameter to tailor the fields that come back for events and alarms.Operator add, modify, and delete events now contain a link to the affected item in a new
modifiedItemblock, deprecating theaccessGroupblock for those event types.All operator events now contain the operator's name at the time.
All events now contain the name of the event's division.
The item search can now filter multiple item types.
Events now show their origin when they have arrived from a remote server (in a multiserver environment).
Access groups have 22 new fields showing the privileges and access they grant their members. These new fields are in the default set for the detail view.
Access groups now return the alarm zones over which they have privilege.
Items on remote servers now show their origin server in a new
serverDisplayNamefield.Cards contain the date and time of their last print or encode, and their issue level at the time.
API changes in 8.30
The items API has methods that let you monitor the status of large numbers of items.
Doors related to guard tour events now appear in an event's
doorblock.Cardholder change tracking lets you synchronise users into an external system by informing it of changes as they occur.
Cardholder group membership hrefs now contain a long string where they used to contain a small integer. Integrations that cache membership hrefs a) should not and b) will need to refresh.
A cardholder's card object now contains a Boolean field called
traceindicating whether its trace flag is set. A card with tracing on generates an event every time it is used.A 'connectedController' field in the door detail returns the ID and name of the door's hardware controller (a hardware controller, not an API controller).
API changes in 8.20
Alarms and events with a related cardholder now show the cardholder's current first and last name in separate fields.
The
namefield on an alarm or event with a related cardholder is now the cardholder's name at the time of the event, rather than at the time of the request.You can use the 'fields' parameter to add the 'details' field to an event summary.
A new field
update_locationon a cardholder gives a link to a POST that changes his or her current location (access zone).An update_cardholder_location call on the Access Zones API returns you the list of Access Zones into which your operator is allowed to move cardholders.
Lockers and locker banks are now visible with the RESTStatus licence, including whether a locker is available or allocated. You still need RESTCardholders to see which cardholder/s a locker is assigned to.
Lockers moved from the locker banks controller into their own. That changed their hrefs.
Bugfix: you can now change a cardholder's division.
A cardholder's relationships block now contains the role holder's two name fields. The
namefield, present since 7.90, is simply these two fields joined with a space, and is therefore ambiguous when a cardholder only has one name.
API changes in 8.10.1112
You can send an override to a locker to open it.
You can use the
fieldsquery parameter to select the fields you receive from locker bank summary and details GETs. The use cases section shows how.
API changes in 8.10
Events now show their related access groups and doors for external event types.
There is a card types API that returns the card types your operator is able to use when assigning cards to cardholders.
There is a PDF API that returns the personal data field definitions your operator is able to see.
You can change the notifications flag on a cardholder's PDF.
Certificates and biometric data are available on PIV cards.
An inputs API gives you the state of 'input' hardware items and lets you override them. It is very similar to the outputs API.
Dummy path for changelog tag.
This path does not exist in the API. It is in the documentation to satisfy the HTML renderers that require a path in every tag.
Response
This result is unlikely.
This path does not exist.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/changelog", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/changelog"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/changelog'const response = await fetch('https://127.0.0.1:8904/api/changelog', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/changelog', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/changelog')
data = response.json()Competencies
Competencies are items to which cardholders can be linked, and which access zones may require on a cardholder before allowing them in.
They have their own access privileges, so operators can see or edit a cardholder's competencies based on the competency's settings as well as the cardholder's division and the privileges the operator has in that division.
Notification settings on the competency cause advance warnings to go to the cardholder and his or her related cardholders (line managers, for example) before the competency expires.
Competencies differ from access groups in that a cardholder can have only one link to a given competency. Attempting to create another will either fail or raise a stateful alarm depending on your version of Command Centre.
The REST API gives you access to competencies through create, search, dereference, update, and delete functions described below.
The Configuration Client online help describes competencies fully.
Licensing
Reading competencies is enabled by the RESTCardholders licence but creating, deleting, or modifying them requires the RESTConfiguration licence.
Search competencies
This returns competencies matching your search criteria.
The result will contain no more than 100 or 1000 competencies depending on your version; you
should follow the next link (if present) for the next batch.
If your result set is empty it means your operator does not have the privilege to view any competencies. Perhaps there are none in the divisions in which your operator has 'View site' or 'Edit site', or your operator has no privileges at all.
A bug in 7.90 meant that this call did not provide next links for sites that had more than
100 competencies. If this is you, set the 'top' parameter as high as you can (as
recommended in the efficiency tips). Command Centre clamps that to a maximum of ten
thousand. If that is not enough competencies for you, you are probably already in contact
with Gallagher technical support.
Get this URL from features.competencies.competencies.href in /api. In the interest of
forward compability, do not build it yourself.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnameshortNamedescriptiondivisionnotesexpiryNotifynoticePerioddefaultExpirydefaultAccessdefaultsdefaultsquerySpecifies which fields to return in the search results. The values you can list are the same as the field names in the details page. Using it you can return everything on the search page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10 you will not. If you are going to send the fields parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The installation lacks a cardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/competencies", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/competencies"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/competencies'const response = await fetch('https://127.0.0.1:8904/api/competencies', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/competencies', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/competencies')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/competencies/2354",
"id": "2354",
"name": "Hazardous goods handling",
"description": "Required for access to chem sheds.",
"serverDisplayName": "ruatoria.satellite.net",
"notes": ""
}
],
"next": {
"href": "https://localhost:8904/api/competencies?skip=1000"
}
}Create a competency
Creates a new competency.
The POST expects a document in the same format as the the competency detail but with far fewer fields. An example is this POST example.
When successful it returns a location header containing the address of the new competency.
Note that you can only create one competency per POST.
Do not code this URL into your application. Take
it from the results of GET /api.
New to Command Centre 9.10.
This requires the RESTConfiguration licence.
Body
This is an example of a PATCH you could use to update a competency, and a POST you could use to create one.
No fields are mandatory in a PATCH, but when using a POST to create a competency you must supply a division.
namestringThe new item's name. If you supply a name and another item of the same type already exists with that name, the call will fail. If you leave it blank in a POST, Command Centre will pick a name for you.
shortNamestringIf you supply a string that is too long, Command Centre will truncate it."
descriptionstringThe new item's description.
divisionobjectThe division to contain this competency.
Mandatory when creating a new competency.
notesstringA string, able to me much longer than description, suitable for holding notes
about the item.
Response
Success. Check the response body for feedback about your request.
The body of the POST did not describe a valid competency. You may have tried to give the new competency the same name as an existing one.
See the body of the response for help.
The operator does not have a privilege on the division that allows creating items inside it ('Configure Site'), or the server has reached its licensed limit of competencies, or it does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/competencies", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/competencies") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/competencies' \
-H 'Content-Type: application/json' \
-d '{
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}'const response = await fetch('https://127.0.0.1:8904/api/competencies', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/competencies', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}
response = requests.post('https://127.0.0.1:8904/api/competencies', json=payload)
data = response.json(){
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}Get details of a competency
You get this URL from a cardholder or from a competency search. In the interest of forward compability, do not build it yourself.
The results document gives everything Command Centre has on a competency except the warning messages that appear on a reader when a cardholder needs to take action.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnameshortNamedescriptiondivisionnotesexpiryNotifynoticePerioddefaultExpirydefaultAccessdefaultsdefaultsquerySpecifies which fields to return. The values you can list are the same as the field names in the details page. Use it to reduce the size of the result document. Separate values with commas.
Treat the string matches as case sensitive.
Response
Success.
The installation lacks a cardholders licence.
Your REST operator does not have the privilege to view this competency.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/competencies/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/competencies/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/competencies/{id}'const response = await fetch('https://127.0.0.1:8904/api/competencies/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/competencies/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/competencies/{id}')
data = response.json(){
"href": "https://localhost:8904/api/competencies/2354",
"id": "2354",
"name": "Hazardous goods handling",
"description": "Required for access to chem sheds.",
"serverDisplayName": "ruatoria.satellite.net",
"notes": "",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "",
"expiryNotify": false,
"noticePeriod": {
"units": "weeks",
"number": 2
},
"defaultExpiry": {
"expiryType": "durationmonths",
"expiryValue": 6
},
"defaultAccess": "fullAccess"
}Remove a competency
This call removes a competency from Command Centre. You get this URL from a cardholder or from a competency search. In the interest of forward compability, do not build it yourself.
New to Command Centre 9.10.
This call requires the RESTConfiguration licence.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Success.
Deleting the competency failed. This happens when it is associated with a cardholder - even if it is inactive.
The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.
That is not the URL of a competency, or the operator is not privileged to view it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/competencies/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/competencies/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/competencies/{id}'const response = await fetch('https://127.0.0.1:8904/api/competencies/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/competencies/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/competencies/{id}')
data = response.json()Update a competency
This is the call you use to update a division's name, short name, description, notes, or division. In the interest of forward compability, take the URL from the results of a division search rather than building it yourself.
The PATCH expects a document in the same format as the the competency detail but with fewer fields. An example is this PATCH example.
New to Command Centre 9.10.
This call requires the RESTConfiguration licence.
Body
This is an example of a PATCH you could use to update a competency, and a POST you could use to create one.
No fields are mandatory in a PATCH, but when using a POST to create a competency you must supply a division.
namestringThe new item's name. If you supply a name and another item of the same type already exists with that name, the call will fail. If you leave it blank in a POST, Command Centre will pick a name for you.
shortNamestringIf you supply a string that is too long, Command Centre will truncate it."
descriptionstringThe new item's description.
divisionobjectThe division to contain this competency.
Mandatory when creating a new competency.
notesstringA string, able to me much longer than description, suitable for holding notes
about the item.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid competency. See the body of the response for help on what went wrong. It may be that you tried to use the name of another competency: no two items of the same type can have the same name. Or you may have tried to set the competency's division to one that is not visible to you.
The operator has a privilege that allows viewing the item but not modifying it, or you tried to set the division to one you cannot configure, or the server is missing the necessary licence.
You need the 'Configure Site' privilege on the item you are changing, which means you need on it on the item's current division. You also need it on the new division, if you are changing that.
This is also the response when the server does not have the 'RESTConfiguration' licence.
That is not the URL of a competency or your operator does not have the privilege to view it. This probably means you have built the URL yourself instead of taking it from the results of a GET.
The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/competencies/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/competencies/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/competencies/{id}' \
-H 'Content-Type: application/json' \
-d '{
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}'const response = await fetch('https://127.0.0.1:8904/api/competencies/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/competencies/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}
response = requests.patch('https://127.0.0.1:8904/api/competencies/{id}', json=payload)
data = response.json(){
"name": "New competency",
"shortName": "C4",
"description": "Translated automatically.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "A very long string."
}Day Categories
A day category links a calendar and a schedule. The calendar determines the days of the year that fall into a day category, and the schedule determines what happens at certain times on those days.
The method in this group gives you the day category hrefs you need when creating and modifying schedules. It is a GET: you cannot create or modify day categories using the API.
Day categories are new to the 8.50 API.
Use case: listing day categories
GET /api.Follow the link at
features.dayCategories.dayCategories.href, appending a search term such asname=substringorname="full name"to filter the selection, andfieldsto tell the server what to return about each day category.Search the results for the day category you are after. With other types of items you would follow the
nextlink until there isn't one, but most sites only have a handful of day categories so they will most likely fit on the first page. Especially if you settop=1000, which is advised.
Licensing
Access to day categories requires one of RESTCardholders, RESTStatus, RESTOverrides, or RESTConfiguration.
Search day categories
This returns the day categories that match your search criteria.
The result will contain no more than 100 or 1000 (depending on your version), or as many as
you asked for more in your request; you should follow the next link, if it is present, to
collect the next batch. Generally a site does not have too many day categories, so if you
set top=1000 you are bound to collect them all.
If your result set is empty it means your operator does not have any of the privileges that allow viewing day categories, such as 'View Site', 'Configure Site', or 'Edit Schedules'. Because day categories do not have divisions, having one of those privileges in any division is enough.
When you have seen them all there will be no next link.
This does not take a division query parameter because day categories are not in divisions.
Do not code this URL into your application. Take it from the 'href' field in the
features.dayCategories.dayCategories section of /api.
Added in 8.50.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
fieldsArray<string>hrefnamedescriptionnotesdefaultsdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the field names in the schema definitions in this document. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The site does not have the RESTCardholders, RESTStatus, or RESTOverrides licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/day_categories", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/day_categories"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/day_categories'const response = await fetch('https://127.0.0.1:8904/api/day_categories', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/day_categories', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/day_categories')
data = response.json(){
"results": [
{
"name": "Default Day Category",
"href": "https://localhost:8904/api/day_categories/3",
"description": "Factory default",
"notes": "The default calendar puts every day in this day category."
}
],
"next": {
"href": "https://localhost:8904/api/day_categories?skip=1000"
}
}Divisions
These methods provide access to the Command Centre divisions that are available to the REST
client. Call /api and use the link at features.{your_feature}.divisions.href to retrieve
the divisions in which the REST operator has privileges for that feature. Pick out the ones of
interest and use those IDs in event, alarm, or item searches if you don't want the search to
scan everything.
Or, you may end up here by following a division's link from a reception item when you need to retrieve the visitor management configuration for that reception's division.
Licensing
Every REST licence enables the divisions controller: RESTEvents, RESTCreateEvents, RESTCardholders, RESTStatus, RESTOverrides, and RESTConfiguration.
Create a division
Creates a new division.
The POST expects a document in the same format as the the division
detail but with far fewer fields. An example is this POST
example. The only mandatory field is parent.
When successful it returns a location header containing the address of the new division.
Note that you can only create one division per POST.
Do not code this URL into your application. Take it from the results of GET /api. That
shows the API features for which you have the necessary licence.
New to 9.30.
Body
The only required field here is the new division's parent. All divisions (except the root) must have a parent.
This is an example of a PATCH you could use to update a division, and a POST you could use to create one.
When POSTing, parent is mandatory.
namestringThe division's name. If you supply a name and another division already exists with that name, the call will fail. If you leave it blank in a POST, Command Centre will pick value for you.
descriptionstringThe division's description.
notesstringA string, able to be much longer than description, suitable for holding notes about
the division.
parentobjectAn object containing an href to the division entity representing the current division's parent.
Required when creating a new division, because only root divisions can be unparented and you cannot create a new one of those.
Response
Success. Check the response body for feedback about your request.
The body of the POST did not describe a valid division. This includes not specifying a parent division. Or it may be that you tried to give the new division the same name as an existing one. Or you may have tried to set the new division's parent to a division that is not visible to you.
See the body of the response for help.
The operator does not have a privilege on the parent division that allows creating divisions inside it ('Configure Site' or 'Edit Divisions'), or the server has reached its licensed limit of divisions or does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/divisions", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/divisions") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/divisions' \
-H 'Content-Type: application/json' \
-d '{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}'const response = await fetch('https://127.0.0.1:8904/api/divisions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/divisions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}
response = requests.post('https://127.0.0.1:8904/api/divisions', json=payload)
data = response.json(){
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}List divisions
The functions inside /api/divisions/ retrieve the divisions in which the operator can
perform other functions. They all return the same data structure.
For example, /api/divisions/view_events retrieves the list of divisions in which the REST
operator has privileges to view events, and /api/division/view_alarms does the same for
alarms.
Do not code these URLs into your application. Take them from the results of GET /api.
For the events and alarms examples, the links will be at events.divisions.href and
alarms.division.href.
/api only shows API features for which you have the necessary licence.
Parameters
topinteger>= 1querySets the maximum number of divisions to return per page.
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
fieldsstringhrefidnameparentvisitorManagementqueryReturn these fields instead of the default set. The values you can list are the same as
the field names you would see in the results, plus visitorManagement, which does not
come out by default. Use it to specify the fields you want in your results. Separate
values with commas.
Treat the string matches as case-sensitive.
Response
Success
The site does not have a REST licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/divisions/_operation_", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/divisions/_operation_"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/divisions/_operation_'const response = await fetch('https://127.0.0.1:8904/api/divisions/_operation_', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/divisions/_operation_', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/divisions/_operation_')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/divisions/2",
"id": "2",
"name": "Root division",
"description": "Contains all other divisions",
"serverDisplayName": "ruatoria.satellite.int",
"parent": {
"href": "https://localhost:8904/divisions/2"
},
"visitorManagement": {
"active": true,
"visitorTypes": [
{
"href": "https://localhost:8904/api/divisions/2/visitor_types/925",
"accessGroup": {
"name": "Visitor access group 1",
"href": "https://localhost:8904/api/access_groups/925"
},
"hostAccessGroups": [
{
"accessGroup": {
"name": "Host access group 1",
"href": "https://localhost:8904/api/access_groups/938"
}
}
],
"visitorAccessGroups": [
{
"accessGroup": {
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
},
{
"accessGroup": {
"name": "Access group 30",
"href": "https://localhost:8904/api/access_groups/927"
}
}
]
}
]
}
}
],
"next": {
"href": "https://localhost:8904/api/divisions/view_events?skip=10"
}
}Get details of a division
Details of a division. Follow the href in a division summary to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringhrefidnameparentvisitorManagementqueryReturn these fields instead of the default set. The values you can list are the same as
the field names you would see in the results, plus visitorManagement, which does not
come out by default. Use it to specify the fields you want in your results. Separate
values with commas.
Treat the string matches as case-sensitive.
Response
Success
The site does not have a REST licence.
That is not the href of a division or you do not have privileges to read divisions (View Site, Configure Site, Edit Divisions, or Advanced User).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/divisions/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/divisions/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/divisions/{id}'const response = await fetch('https://127.0.0.1:8904/api/divisions/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/divisions/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/divisions/{id}')
data = response.json(){
"href": "https://localhost:8904/divisions/2",
"id": "2",
"name": "Root division",
"description": "Contains all other divisions",
"serverDisplayName": "ruatoria.satellite.int",
"parent": {
"href": "https://localhost:8904/divisions/2"
},
"visitorManagement": {
"active": true,
"visitorTypes": [
{
"href": "https://localhost:8904/api/divisions/2/visitor_types/925",
"accessGroup": {
"name": "Visitor access group 1",
"href": "https://localhost:8904/api/access_groups/925"
},
"hostAccessGroups": [
{
"accessGroup": {
"name": "Host access group 1",
"href": "https://localhost:8904/api/access_groups/938"
}
}
],
"visitorAccessGroups": [
{
"accessGroup": {
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
},
{
"accessGroup": {
"name": "Access group 30",
"href": "https://localhost:8904/api/access_groups/927"
}
}
]
}
]
}
}Remove a division
This call removes a division from Command Centre.
Deleting divisions will be possible in a future version of Command Centre.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Deleting the division failed. This happens when the division is not empty or is still being used by another item (such as an operator group).
The operator has the permission to view the division but not delete it, or the server is not licensed for the operation.
That is not the URL of a division, or the operator is not privileged to view it, or the server does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/divisions/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/divisions/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/divisions/{id}'const response = await fetch('https://127.0.0.1:8904/api/divisions/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/divisions/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/divisions/{id}')
data = response.json()Update a division
This is the call you use to update a division's name, description, notes, or parent.
The PATCH expects a document in the same format as the the division detail but with fewer valid fields. An example is this PATCH example.
New to 9.30.
Body
There are no mandatory fields, but it would not be much of an update without one.
This is an example of a PATCH you could use to update a division, and a POST you could use to create one.
When POSTing, parent is mandatory.
namestringThe division's name. If you supply a name and another division already exists with that name, the call will fail. If you leave it blank in a POST, Command Centre will pick value for you.
descriptionstringThe division's description.
notesstringA string, able to be much longer than description, suitable for holding notes about
the division.
parentobjectAn object containing an href to the division entity representing the current division's parent.
Required when creating a new division, because only root divisions can be unparented and you cannot create a new one of those.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. The response body will contain feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid division. See the body of the response for help on what went wrong. It may be that you tried to use the name of another division: no two items of the same type can have the same name. Or you may have tried to set the division's parent to a division that is not visible to you.
The operator has a privilege that allows viewing the division but not modifying it, or you tried to set a parent on the root division, or you tried to set the parent to a division you cannot configure, or the server is missing the necessary licence.
You need either the 'Configure Site' or 'Edit Divisions' privilege on the division you are changing. You also need it on the new parent, if you are changing that.
This is also the response when the server does not have the 'RESTConfiguration' licence.
That is not the URL of a division or the operator does not have the privilege to view that division. This probably means you have built the URL yourself instead of taking it from the results of a GET.
The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/divisions/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/divisions/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/divisions/{id}' \
-H 'Content-Type: application/json' \
-d '{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}'const response = await fetch('https://127.0.0.1:8904/api/divisions/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/divisions/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}
response = requests.patch('https://127.0.0.1:8904/api/divisions/{id}', json=payload)
data = response.json(){
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}Doors
These methods give you read access to basic data about Doors in the Command Centre database, and let you open them.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of doors, each containing the fields you ask for in the query (including, for example, the URLs to open them).
Half-cycle doors and the lock override
Doors with half-cycle locks differ from regular doors in that once unlocked with an 'open' override or a card badge, they stay unlocked until physically opened then closed, or re-locked with a 'lock' override (added in 9.40). These doors normally require a physical key to open in addition to the electronic unlock. They are popular in correctional facilities.
Door status flags
If the door is online, its statusFlags field will contain one or more of these flags:
forcedmeans the door was opened or unlocked while secure.openTooLongmeans the door has been open for longer than its configured DOTL time.tampermeans one of the door's inputs is in a tampered state. The usual cause of that is a resistance moving outside nominal range, meaning the input has been cut or shorted.openmeans the door has a sensor for detecting its openness and it is reporting as such.closedis the inverse. The door is closed or has no open sensor.lockedmeans the door locked. It reflects the state of the door's unlock sensor, if it has one. Otherwise it reflects the state of the door's unlock output, if it has that. Without an unlock output, a door is not capable of access control.unlockedmeans 'locked' is not set.securemeans the door's normal state is closed and locked.freeis the opposite of 'secure': nobody needs to badge to open it.
secure and free do not change when the door opens. They are about whether the door is
enforcing access control, not about the current state of the door hardware.
When allowing passage, a door normally moves from closed and locked to closed and unlocked, to open and unlocked (extremely briefly), then to open and locked while someone is walking through it, then back to closed and locked. It will be secure throughout.
Door flag rules
- If and only if the door is online, exactly one of 'closed' or 'open' will appear, and one of 'locked' or 'unlocked', and one of 'secure' or 'free'.
So, to establish if the door is in a normal state, look for 'closed' or 'open'. If neither is present, your door is in an error state.
Use cases
Listing Doors
GET /api.- Follow the link at
features.doors.doors.href, appending a search term such asname=substringto filter the selection if you have a lot of doors, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink until there isn't one.
Opening a Door
- Find the href for the door using the process above.
- GET it.
- POST to the
open↪ URL in thecommandsstructure of the results.
Finding a door's status
- Find the href for the door using the process above, and GET it.
- Follow the
updateshref from that page. - Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search doors
This returns a summary of the doors matching your search criteria.
The result will contain no more than 100 or 1000 doors (depending on your version), or as
many as you asked for more in your request; you should follow the next link, if it is
present, to collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any doors, such as 'View Site', 'Edit Site', or 'Override - Open Door'. Perhaps there are no doors in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.doors.doors section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerentryAccessZoneexitAccessZonestatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/doors", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/doors"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/doors'const response = await fetch('https://127.0.0.1:8904/api/doors', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/doors', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/doors')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/doors/332",
"id": "332",
"name": "Front door"
}
],
"next": {
"href": "https://localhost:8904/api/doors?skip=1000"
}
}Get details of a door
This returns the detail of one door.
Follow the 'href' field in a door summary to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerentryAccessZoneexitAccessZonestatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent a door, or the operator does not have a privilege on the door's division that allows viewing them, such as 'View Site', 'Edit Site', or 'Override
- Open Door'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/doors/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/doors/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/doors/{id}'const response = await fetch('https://127.0.0.1:8904/api/doors/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/doors/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/doors/{id}')
data = response.json(){
"href": "https://localhost:8904/api/doors/332",
"id": "332",
"name": "Front door",
"description": "Main lobby doors.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"entryAccessZone": {
"name": "Roswell building 2 lobby",
"href": "https://localhost:8904/api/access_zones/3280"
},
"exitAccessZone": {
"name": "Roswell building 2 cafeteria",
"href": "https://localhost:8904/api/access_zones/50"
},
"notes": "Multi-line text...",
"shortName": "Short text",
"updates": {
"href": "https://localhost:8904/api/doors/332/updates/0_0_0"
},
"statusFlags": [
"secure",
"closed",
"locked"
],
"commands": {
"open": {
"href": "https://localhost:8904/api/doors/332/open"
},
"lock": {
"href": "https://localhost:8904/api/doors/332/lock"
}
},
"connectedController": {
"name": "Third floor C6000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
}
}Lock a door
Sends an override to lock a half-cycle door that is in the unlocked state, rather than letting it wait to open and close.
Follow the commands.lock.href field in a door to get here.
Added in 9.40.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding doors (such as 'Override - Open Door').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/doors/{id}/lock", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/doors/{id}/lock"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/doors/{id}/lock'const response = await fetch('https://127.0.0.1:8904/api/doors/{id}/lock', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/doors/{id}/lock', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/doors/{id}/lock')
data = response.json()Open a door
Sends an override to unlock a door.
Follow the commands.open.href field in a door to get here.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding doors (such as 'Override - Open Door').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/doors/{id}/open", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/doors/{id}/open"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/doors/{id}/open'const response = await fetch('https://127.0.0.1:8904/api/doors/{id}/open', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/doors/{id}/open', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/doors/{id}/open')
data = response.json()Monitor a door
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in a door summary or details pages to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringstatusstatusTextstatusFlagsqueryThis instructs the server to return these fields in the update, instead of the default set. You will not hear about updates to fields you do not list.
Response
Success. See the introduction for a description of the three status fields.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent a door, or the operator does not have a privilege on the door's division that allows viewing them, such as 'View Site', 'Edit Site', or 'Override
- Open Door'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/doors/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/doors/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/doors/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/doors/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/doors/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/doors/{id}/updates')
data = response.json(){
"updates": {
"status": "Closed, Locked, Secure access.",
"statusText": "Closed, Locked, Secure access.",
"statusFlags": [
"closed",
"locked",
"secure"
]
},
"next": {
"href": "https://localhost:8904/api/doors/332/updates/9_1"
}
}Elevator Groups
These methods give you read access to Elevator Groups.
The reason you would want that is to allocate default floors to cardholders. Each cardholder can have one default floor per elevator group, so that when they badge into that group's lobby area the elevator system can arrange a car to take them to their favourite floor.
Each elevator group only goes to certain floors, so to give a cardholder a default floor you need to see which floors each elevator group services.
Command Centre represents floors with access zones. If an elevator car has two doors, front and rear, it may service two access zones on the same physical floor. A cardholder could pick either of those access zones as their default for that elevator group.
The main entry point is a paginated search that returns what you need to pick default floors for a cardholder, limited by the privilege that enables that operation on a cardholder. That is the call that you are most likely to need, but there is another that gives you all elevator groups that your operator can view, rather than the smaller set of groups your operator can use in a cardholder edit.
Use cases
Choosing and setting a cardholder's default floor
Your client will first need to list all elevator groups, and the floors and access zones on those elevator groups, so that it can pick from them (if knows them by name already) or present a list and allow a user to pick one if it is interactive.
Then it will need to send a PATCH back to the cardholder to set his or her default floor for an elevator group.
Listing elevator groups and their access zones
GET /api.- Follow the link at
features.cardholders.modifyPassengerDetails.href, appending a search term such asname=substringto filter the selection if you have a lot of elevator groups. - Find the elevator group you are after, following the
nextlink if you have lots. - Look in the
floorAccessarray for the floor names and access zone names you can use for picking the floor, and the access zone hrefs to use in the PATCH coming up.
Setting a cardholder's default floor
Find the href for the cardholder.
PATCH it with a request body containing the hrefs of the elevator groups and access zones you wish to set as that cardholder's defaults.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status.
If you have RESTCardholders but not RESTStatus in 8.60 or later, the GETs return cardholder-related fields.
In 8.50 and older, the GETs fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search assignable elevator groups
This searches the elevator groups that your privileges allow you to use in cardholders' default floor and passenger type assignments, returning everything you need to make those assignments.
The result will contain no more than 100 or 1000 depending on your version, or as many as
you asked for more in your request; you should follow the next link, if it is present, to
collect the next batch.
If your result set is empty it means your operator does not have the privilege to assign elevator groups to cardholders ('Modify Passenger Details'). Perhaps there are no elevator groups in the divisions in which your operator has that privilege.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the href field in the
features.cardholders.modifyDefaultFloors section of /api.
It requires the RESTCardholders licence.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefnamedescriptiondivisionfloorAccessdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. An array of elevator group
objects and a next link for more.
See the note in the description about privileges if your result set is empty.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details'const response = await fetch('https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/elevator_groups/635",
"name": "Main building lower floors",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"floorAccess": [
{
"floorNumber": 1,
"frontService": true,
"rearService": true,
"floorName": "Level 1",
"frontAccessZone": {
"id": "637",
"name": "Lvl 1 lift lobby",
"href": "http://localhost:8904/access_zones/637"
},
"rearAccessZone": {
"id": "638",
"name": "Lvl 1 lift lobby rear",
"href": "http://localhost:8904/access_zones/638"
}
}
]
}
],
"next": {
"href": "https://localhost:8904/api/elevator_groups/635"
}
}Search elevator groups
This returns the name and href of the elevator groups matching your search criteria. This
uses a different privilege from the modify_default_floors call, so it may not return you
the groups you need. If your goal is to set cardholders' default floors, you should that
call instead.
The result will contain no more than 100 or 1000 (depending on your version), or as many as
you asked for more in your request; you should follow the next link, if it is present, to
collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any elevator groups, such as 'View Site' or 'Edit Site'. Perhaps there are no elevator groups in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the href field in the
features.elevators.elevatorGroups section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefnameshortNamedescriptiondivisionnoteselevatorSystemelevatorGroupNumberfloorAccessrearAccessEnabledgroundFloorNumberdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. An array of elevator group objects
and a next link for more.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/elevator_groups", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/elevator_groups"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/elevator_groups'const response = await fetch('https://127.0.0.1:8904/api/elevator_groups', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/elevator_groups', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/elevator_groups')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/elevator_groups/635",
"name": "Main building lower floors"
}
],
"next": {
"href": "https://localhost:8904/api/elevator_groups?skip=1000"
}
}Get details of an elevator group
This returns the detail of one elevator group.
If you are setting cardholders' default floors, you should be using the
modify_default_floors call rather than this one.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringhrefnameshortNamedescriptiondivisionnoteselevatorSystemelevatorGroupNumberfloorAccessrearAccessEnabledgroundFloorNumberdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an elevator group, or the operator does not have a privilege on the elevator group's division that allows viewing them, such as 'View Site' or 'Edit Site'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/elevator_groups/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/elevator_groups/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/elevator_groups/{id}'const response = await fetch('https://127.0.0.1:8904/api/elevator_groups/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/elevator_groups/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/elevator_groups/{id}')
data = response.json(){
"href": "https://localhost:8904/api/elevator_groups/635",
"name": "Main building lower floors",
"description": "Main building lobby elevator group.",
"notes": "Multi-line text...",
"shortName": "Short text",
"elevatorGroupNumber": 1,
"elevatorSystem": {
"id": "632"
},
"rearAccessEnabled": true,
"groundFloorNumber": 1
}Events
Use the GET methods in this API for historical searches or to stay up to date with new events as they occur. Use the POST method (added in version 8.10) to create events of your own.
There are no PATCH actions on events, because they are immutable. If an event is also an alarm it carries some changeable state and a log of activity and comments, but the underlying event never changes.
The API only returns events that are still in Command Centre's database. It will not return events that have been removed, even if they are in an archive file.
Location events in 9.00
Command Centre has about 80 event types that occur when somebody authenticates at a device, usually by badging a card. Some of those event types also contain the access zones that the person started and finished in. Version 9.00 introduced some improvements that presented the cardholder and the two zones to API clients in a consistent way:
A new event type
hasLocationthat lets you filter for only those events that reveal a cardholder's location. If your server is 9.00 or better, use it in thetypequery parameter as you would normally use an integer event type:type=hasLocation. Gallagher will update this as new versions add more event types. Do not use it on 8.90 or earlier: those servers will reject it.A new
locationblock on events of those types that contains fields containing the cardholder and their location.
For more on using these new features, see the relevant use case below (search for 'location').
Event use cases
Downloading the entire event database
GET /api- Follow the link at
features.events.events.href↪. You can add query parameters to alter the search. If you are running 8.70 or later and there is a danger that you might encounter a problem while processing a batch of events you should also usefieldsto get thenextlink for each event. Note that this is different from thenextlink you receive at the end of each result set. - Process the events you receive in that call. If you asked for the
nextfield, write the link for each event you successfully process to disk before attempting the next one. Then, if the worst happens, your client can read that link from disk and pick up where it left off. - If there were results, follow the link at
next.hrefand repeat.
If you then wish to stay up to date, switch to following the link at updates.href
↪ in the results. That call will block until more
events arrive, or a minute passes (approximately).
Downloading the most recent events
Get /api- Take the link at
features.events.events.href↪ and appendprevious=true&top=20. Add the appropriate query separator?or&first, depending on whether there is a query parameter in the URL already, and change the 20 as appropriate. The default is 1000, which is probably more than you want. - Process the events you receive in that call.
- Follow the link at
previous.hrefto get earlier events, ornext.hreforupdates.hreffor later events. The last is a long poll, which means that if no events are ready when you make the call it will block until new events arrive.
Reporting on events over a period.
GET /api- Follow the link at
features.events.events.href↪ appendingafter=2017-01-01Z&before=2017-02-01Zor whatever timestamps are appropriate (after the correct query separator,?or&).
Remember that some remote systems take their time sending events to Command Centre, so do not be too hasty running your reports. If you fire them off at the stroke of midnight, you may miss events that occurred before midnight but have not yet arrived at Command Centre.
Receiving new events as they occur, starting from now
GET /api- Follow the link at
features.events.updates.href↪ adding query parameters containing your search terms. The call will block until at least one matching event arrives at the server. - Process the events you receive in that call. Often there is only one: the first that arrived after you made the call.
- Sleep to reduce load on the server.
- Loop, following the link at
updates.hrefafter processing each batch of events. It will return immediately if there are new events waiting, or it will block until new events arrive. Follow the advice in downloading the entire event database about keeping track of your position if you might encounter a problem writing events mid-batch. Note that that advises using thenextfield, but you will be using theupdatesfield, which is the same thing in long-poll form.
Receiving new events as they occur, starting from now, with no long polls
GET /api- Follow the link at
features.events.events.href↪ and appendprevious=true&top=1. Add the appropriate query separator?or&first, depending on whether there is a query parameter in the URL already. The call will return one event (which you can ignore) and anextlink you'll need later. - Sleep and loop, following the link at
next.hrefafter processing each batch of events. It will return immediately whether or not there are new events waiting. Follow the advice in downloading the entire event database about keeping track of your position if you might encounter a problem writing events mid-batch.
Receiving new events as they occur, starting in the past
GET /api- Follow the link at
features.events.events.href↪ appending the query parameterafter=2021-05-08Z(or whatever timestamp is appropriate). - Process the events you receive in that call.
- Sleep to reduce load on the server.
- Loop, following the link at
updates.hrefafter processing each batch of events. It will return immediately if there are new events waiting, or it will block until new events arrive. Follow the advice in downloading the entire event database about keeping track of your position if you might encounter a problem writing events mid-batch. Note that that advises using thenextfield, but you will be using theupdatesfield, which is the same thing in long-poll form.
Searching for events related to particular cardholders
GET /apiFollow the link at
features.items.items.href↪, addingname="your_cardholder_name"&type=1to the query after the appropriate separator (?or&).The 'items' controller necessary for that step is available with the RESTEvents licence. If you also have the RESTCardholders licence you could use the link at
features.cardholders.cardholders.href↪ instead, adding a separator andname="your_cardholder_name".In either case, remove the quotes if you want a substring search and can handle more than one cardholder in the results.
Extract the item ID of your cardholder or cardholders from that page, repeating as necessary for additional cardholders.
Follow the link on the
/apipage atfeatures.events.events.href↪ appending the separator andcardholder=XXorcardholder=XX,YY,ZZwith the cardholders' IDs.
To further improve the efficiency of your search, filter by event types and a time range.
Searching for events that indicate location or movement
If your server is running 8.60 or earlier, use the search filter
type=20001,20002,20003,20047,20107,15582,15583,15800,15808,42415. That is not a complete list
of movement event types but it includes the popular ones.
If your server is running 8.70-8.90 inclusive, give your operator the 'View cardholder events' privilege instead of 'View events'. Then you can request all events, and the server will only send you movements because that is all your operator is permitted to see.
If your server is running 9.00 or later, the 'View cardholder events' privilege is still an
excellent idea to limit your operator's vision but you can also use the search filter
type=hasLocation&fields=location (with a leading ? or & of course, and more fields if you
need them). That will limit the results to location events.
When using a server at version 9.00 or better:
GET /apiGet the URL of the head of the event queue by following the link at
features.events.updates.href↪ appendingtype=hasLocation&fields=locationwith the appropriate query separator?or&depending on whether there is a query parameter in the URL already. The call will block until a new location event arrives.If this is your first time here you will have an array containing one event. If you looped up from a later step, the array may contain many more. In any case, process all the events in the results using the advice immediately below about interpreting the
locationblock. Also follow the advice in downloading the entire event database about keeping track of your position if you might have to abort mid-batch. Note that that advises using thenextfield, but if you want long polls you will be using theupdatesfield.Sleep for a short time to reduce load on the server.
Follow the link at
updates.hreffor a long poll that waits for new events to arrive, or follownext.hreffor a call that will return immediately even if there are no new events for you.Loop up to process the events you received.
Interpreting the location block:
If you are simply after a person's location and do not care how they arrived there, use the
afterLocationblock insidelocation. ThecanonicalTypeNamefield in there will tell you what kind of location it is (reception or access zone). The schema definition describesafterLocationin more detail.If you are after movements, which happen when a door grants a person access or an operator moves them on a tag board or via the API, and happen to some types of visitors when their host moves, ignore all events except those that have a
location.typeofmoved. Look at theafterLocationto see where they landed. There will also be abeforeLocationif the door had two zones configured on it, in case you're interested in where they came from. ThecanonicalTypeNamefield in both blocks will tell you what kinds of item they are.If you are after denials, which happen when a person authenticates but fails the access check, look at the events that have a
location.typeofdenied. Like 'moved'-type events,location.afterLocationwill show where they ended up. The difference is that with 'denied'-type events, they started there too.
Each of those fields is covered in the schema definition.
Be aware that the events you receive are limited to those with a source item that is in a division in which your operator has a privilege that allows viewing events. In 9.00 those privileges are 'View Events' and 'View Cardholder Events', and of course 'Advanced User'. That makes this API unsuitable for handling emergencies such as evacuations if your operator's view is limited: if your operator does not have the privilege to view events generated by a particular door, this API will not tell you about movements through that door.
Searching for events coming from other items
Events such as 'access granted' and 'zone count maximum' come from doors and access zones. To
search for them, follow the same process as the previous use case (getting cardholder events)
but use the source filter parameter instead of cardholder. It will limit the results to
just those events that came from the items you gave in the query. If you have the RESTStatus
licence you can search for access zones, alarm
zones, fence zones, macros, outputs, doors, and (in 8.10) inputs. If you do not, or if your
source is not one of those types, use the items API with a suitable type filter.
You can also use the type parameter to limit the events to particular event types. By doing
this you can (for example) subscribe to 'access granted' events from a collection of doors.
New in v8.70, you can also filter by relatedItem. Use this to find events related
to item or items regardless of the type.
Creating a new event
GET /api- POST to the link at
features.events.events.href↪
There are some rules around creating events, so you should first have a careful read of the POST documentation.
Listing all event types
GET /api- GET the link at
features.events.eventGroups↪
That will return all event types in their groups.
Event 'updates' versus 'next'
This expands on the difference between the API endpoints at the ends of the long poll updates
and short poll next links that you receive in event searches. It will make more sense after
reading the operations' own sections (long poll, short
poll).
The two API calls use the same filters and return the same payloads, including the same pagination links. They differ at two times:
On your first call, the short-polling 'events' route will return you the first events in the historical record that meet your search criteria but the long-polling 'updates' route will wait until new events arrive.
On the last call, when there are no events that meet your criteria that the server has not sent you already, 'events' will immediately return an empty result set but 'updates' will wait for more to arrive.
The short poll is appropriate when you do not wish to stay up-to-date. When you are extracting events from the past, and want to move on to other things as soon as you have them all. Generating reports, basically.
The long poll is appropriate when you will be continuing to make the call, probably forever, because you wish to stay current and to not miss any events.
You should only be using one of the two. Using both in the same integration may indicate a design issue.
Efficiency tips
Use search parameters to reduce the filtering burden on the server.
When downloading a significant number of events, leave
topat 1000 or more, provided your client can handle results over a megabyte (events are around 1 KB each). Performance tests have shown that throughput decreases dramatically iftopis too low.If using 8.40 or later, use its
fieldquery parameter to cut back on the fields the server sends to you. Not only will it save bandwidth, but it will save the server looking up all those values and serialising them for you.If you are using the
updateslink to keep up to date with events, sleep between calls for as long as your requirements allow. Doing that will improve the likelihood of your collecting more than one event when it is busy.
For example, if Command Centre is generating ten events per second and you do not sleep between REST calls, you will be callingupdatesten times per second for one event each time. However if you sleep for two seconds after each call you will receive 20 events at a time, saving CPU and I/O.
Event field specifiers
What you can do with the fields query parameter on an events method has improved through the
versions, so explaining it is a topic in itself.
In 8.40 and later the values you can list are the same as the field names in the details
page, plus the special value for a personal data field described
below. You can pick whichever fields you want, including defaults, though we urge you to
only list the fields you need. Anything you wrote for 8.30 or earlier will
work in 8.40 as it did before.
In versions up to 8.30 you can only add fields to the event summary and details pages, not
remove them. If you send the parameter it must start with fields=defaults. That gives you
the default set. What you can add after that depends on the version of Command Centre you're
calling.
In 8.10 and earlier the only field you can add is cardholder.pdf_XXXX, where XXXX is the ID
of a PDF. Find that ID with a query to the PDFs
controller. Don't forget a
separating comma between it and defaults.
That will add a cardholder's PDF to the events that are related to them.
You can only pick one PDF. It will only appear on events that have a related cardholder, such as access events, because without a cardholder there is no PDF. The security model applies too, so it will only appear if your REST operator has the appropriate privileges on that cardholder and PDF.
In 8.20 and later you can also add details. Make sure you have a separating comma after
defaults or cardholder.pdf_XXXX.
An event detail string can be kilobytes long so we left it optional. It is the only field in the details page that is not in the search results, by the way, so if you are running 8.20 or later you can add it to your search results and you will not need the details page.
| Server version | Valid fields parameter values |
|---|---|
| Older than 8.20 | defaults,cardholder.pdf_XXXX |
| 8.20 to 8.30 | defaults,cardholder.pdf_XXXX or defaults,details or defaults,cardholder.pdf_XXXX,details |
| 8.40 and later | any |
Per-event bookmarks
If you are running 8.70 or later and you put previous, next, or updates in the field
list of an event search, the server will add those fields to each event. All server versions
include links with those names at the end of results, but they are not useful if you crash out
half way through processing a batch. The per-event links, only available in 8.70 and later
and only on request, give you a place to pick up from without being re-sent the events you
already processed.
Licensing
The GETs that collect alarms and events require the RESTEvents licence.
The POST that creates events requires RESTCreateEvents.
The GET to collect event types requires RESTEvents or RESTCreateEvents.
Versions
The body of this document clearly indicates when recent features arrived in the API so that readers with older versions of Command Centre know not to expect them.
Search events
This returns the next batch of events matching the supplied filters starting
at the beginning of the database, or at the time specified by the
after parameter.
For its correct use in various scenarios, see the use cases.
By default the result will contain no more than 1000 events; for
efficient transfer of large numbers of events you should increase this
with the top parameter in the request URL.
Each response will contain a next and an updates link. Following
the next link will return the next batch of events, or an empty list
if there are no more available. Following the updates link will also
return immediately if more events are available, but if there are none,
it becomes a long poll. It will will block until an event is available
that matches the specified filters, or a timeout passes.
Do not code this URL into your application. Take it from events.events.href in the
results of GET /api.
Parameters
topinteger[1, 10000]1000querySets the maximum number of events to return per page.
afterstring<date-time>queryRestricts events to those that occurred at or after this time.
The server accepts extended ISO-8601 time stamp formats. There must be hyphen separators
in the date and colons in the time, and a T separating the two.
For predictable results you should also add a timezone specifier. A Z means UTC and is
recommended, but +hh, +hhmm, and +hh:mm also work. Note that you need to encode
plus-signs as %2b in a URL.
If you omit the time, the server will assume midnight. If you omit just the seconds, the
server will take it to the top of the minute, :00.
beforestring<date-time>queryRestricts events to those that occurred before this time. Events that occurred at this
exact time (to the second) will not appear in the results. For example, to collect all
events that occurred on 1 January 2017, use after=2017-01-01Z,before=2017-01-02Z. You
will not receive any events from 2 January.
The after parameter (above) describes the time stamp formats that the server accepts.
sourceArray<string>queryRestricts events to those whose source item has this ID. Separate multiple IDs with commas. Use the items API to search Command Centre's items.
typeArray<string>queryRestricts events to those whose type is in this list. Separate multiple IDs with commas.
The results may include other event types if you also use the group parameter
because the type and group fields are combined as an 'OR', not an 'AND'.
Use the event groups call to see all event types. The IDs you need for this query parameter are strings but look like small integers. That call shows the event types along with the groups they are in, so do not confuse event types with event groups. They are separated in the results of the API call, but as a general rule you can tell them apart by their integer value: event group IDs are in the hundreds, while most event type IDs are in the thousands.
In version 9.00 and later use the special value hasLocation to include all event types
that can reveal a cardholder's location or access zone. In 9.00 it is a set of about 80
event types; it may be more in future versions. If you use hasLocation you probably
also want to ask for the location block using the fields query parameter.
Do not send type=hasLocation to a server running 8.90 or earlier: it will reject it.
groupArray<string>queryRestricts events to those with this event group ID. Separate multiple IDs with commas.
group is ORed with type. In fact it is shorthand for type= followed by the IDs of
all the event types in the event type groups you list, and the search uses the union of
the two lists.
Use the event groups call to see all event types and groups.
cardholderstringqueryRestricts events to those associated with the cardholder that has this Command Centre ID. Separate multiple IDs with commas.
divisionArray<string>queryRestricts events to those in these divisions or their descendants. Separate IDs with commas.
A more secure option for limiting your client's visibility is to set the operator's privileges so that it only has access to those divisions.
directDivisionstringqueryRestricts events to those whose division is in this list. Unlike division=, it does not follow ancestry. The name directDivision is case-sensitive. Separate IDs with commas.
Example: division=2,101.
relatedItemstringqueryRestrict events to those associated with the item that has this Command Centre ID. Separate multiple IDs with commas.
Example: relatedItem=3,102
fieldsstringdefaultsdetailscardholder.pdf_*hrefidserverDisplayNametimemessageoccurrencespriorityalarmoperatorsourcegrouptypeeventTypedivisioncardholderentryAccessZoneexitAccessZonedooraccessGroupcardmodifiedItemlastOccurrenceTimepreviousnextupdateslocationqueryIn 8.40 and later the values you can list are the same as the field names in the details
page, plus a special field name for a personal data field,
described next. You can pick whichever fields you want, including defaults though we urge
you to avoid that and only ask for the fields you need.
You can request cardholder.pdf_XXXX where XXXX is the ID of a PDF, which you can discover
with a query to the PDFs
controller. That will add a
cardholder's PDF to the events that are related to them.
You can only pick one personal data field. It will only appear on events that have a related cardholder, such as access events, because without a cardholder there is no personal data. The security model applies too, so it will only appear if your REST operator has the appropriate privileges on that cardholder and PDF.
previousbooleanfalsequeryReturns the newest events rather than the oldest. Without this option the API will return
events starting from the epoch, but if you set it to true the server will return the
most recent events, the last of which will be the latest to arrive at the server.
In both cases you can move backward and forward in arrival time with the 'next' and 'previous' links.
posinteger>= 0queryINTERNAL USE ONLY. This is how Command Centre tracks the events you have seen already. Do not set it yourself. Retain the 'next' or 'updates' link in your application instead.
Response
Success
The site does not have a RESTEvents licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/events", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/events"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/events'const response = await fetch('https://127.0.0.1:8904/api/events', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/events', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/events')
data = response.json(){
"events": [
{
"href": "https://localhost:8904/api/events/61320",
"id": "61320",
"serverDisplayName": "ruatoria.satellite.int",
"time": "2016-02-18T19:21:52Z",
"message": "Operator logon failed for FT Workstation on GNZ-PC1439",
"occurrences": 2,
"priority": 3,
"alarm": {
"state": "unacknowledged",
"href": "https://localhost:8904/api/alarms/61320"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Chong, Marc"
},
"source": {
"id": "321",
"name": "FT Workstation on GNZ-PC1439",
"href": "https://localhost:8904/api/items/321"
},
"group": {
"id": "35",
"name": "Invalid Logon"
},
"type": {
"id": "601",
"name": "Operator logon failed"
},
"eventType": {
"id": "601",
"name": "Operator logon failed"
},
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2",
"name": "Root division"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"id": "325",
"name": "Bruce, Jennifer",
"firstName": "Jennifer",
"lastName": "Caitlin"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Brookwood showroom",
"id": "333"
},
"exitAccessZone": {
"href": "https://localhost:8904/api/access_zones/913",
"name": "Compressor room",
"id": "913"
},
"door": {
"href": "https://localhost:8904/api/doors/745",
"name": "Main hoist door"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"card": {
"facilityCode": "A12345",
"number": "78745",
"issueLevel": 1
},
"modifiedItem": {
"href": "https://localhost:8904/api/cardholders/325",
"type": {
"id": "1",
"name": "Cardholder"
}
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"previous": {
"href": "https://localhost:8904/api/events?pos=61320&previous=True"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}
],
"previous": {
"href": "https://localhost:8904/api/events?previous=True&pos=61320"
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}Add an event
Use this method to create an event in Command Centre v8.10 or later.
Do not code this URL into your application. Take it from events.events.href in the
results of GET /api.
Each field has particular rules and has its own effects on the event and subsequent reports, and misconfiguration (such as inadvertently causing a macro to run itself) can land you in real trouble, so have a good look at the documentation below and the example POST body.
Events are immutable: you cannot PATCH or DELETE them after you create them.
Usable event types
You must supply an event type. At the time of writing (9.50), CC ships with 30 event types you can use, each in its own event type group. The POST body schema definition linked above shows which event types you can use: they have 'external' in their name.
You can create 970 of your own event types using the External Event Type Configuration Utility, a separate Windows application that lets you create external event types and make them appear on items' Event Response and Alarm Instructions tabs in the Configuration Client. You will find the release note for that utility in the Documentation folder on the installation media.
1000 event types sounds like a lot, but be aware that you cannot delete event types, and the only thing you can modify on an existing event type is its name. Their event type group and item types are permanent once you save them from the utility. Please plan carefully, and take backups!
A note on event type groups
There is much confusion between event types and groups.
You do not specify a type group when creating a new event. You specify a type. The parts of Command Centre that work with the event later will look up what group the event type is in, principally to select an action plan to run, but you do not need to consider groups when creating the event.
Usable source items
Every event needs a source item. You can let the server pick one for you (it will use the REST Client item identified by your API key) or you can use any item that has an 'Event Response' tab in its Configuration Client window (which is most of them). Our existing integrations use items like doors, external system items, and cameras.
If you allow the server to default to your REST client item as the source, or if specify it yourself, your operator must have 'Create events and alarms' in at least one division for the call to succeed. Any division will do. Otherwise you'll receive a 403.
If you specify a source item to a server running 8.90 or later and that item is not your REST Client item, your operator must have the 'Create events and alarms' privilege in that item's division. In older versions it was enough for your operator to have that privilege in any division.
Cardholders cannot be event sources because they do not have an 'Event Response' tab, but...
Related items
Along with a source, you can link other items to the events you create. If you link a cardholder, for example, your events will show on an activity report generated for that cardholder. To link an item your operator must have a privilege that allows viewing it ('View cardholders' for cardholders or 'View site' for most other item types).
Action plans
An event can fire an action plan, which will
- set the priority if the REST client did not set one in the body of the POST, and
- run a macro on the server.
Macros are extremely powerful, and a thorough treatment requires more room than we have, so it is sufficient to say that you should not aim for a dramatic first test. A good first result is to turn on a virtual output made for the purpose. Just make sure that it will not trigger another macro because it is possible to create loops, causing havoc.
You do not pick an action plan to run when you POST your event. Command Centre does that in three steps:
- The server looks at the configuration of the source item in the Event Response tab of its property page in the Configuration Client. If there is an entry for the event type that is not "use default", Command Centre will fire that action plan and skip the next steps. In versions older than 8.30 the control is per event group, not per event.
- Since the server did not find an action plan on the item, it tries its alarm zone. Specifically, the Event Defaults tab of the alarm zone's property page in the Configuration Client. Again the control is per event group not per event type in older versions. If the event's action plan is not 'use default', the server fires that action plan and goes no further.
- Since the server did not find an action plan on the item or its alarm zone, it looks at the configuration in the Event Defaults tab of the server properties. There is always an action plan there, even if it does nothing more than set the priority of the event.
Once it has found the action plan to run, the server will assign the event its priority (if you did not specify a priority yourself) and run the macro if there is one, both from the 'Command Centre' tab of the action plan's property page in the Configuration Client. The server will not use the configuration from the other tabs.
It is not possible to submit an event with priority zero, but it is possible to submit an event with no priority, and have the action plan assign it priority zero. This will run the macro on the action plan then drop the event before it reaches the database.
Alarm instructions
Alarm instructions are marked-up text fields that Command Centre presents to security personnel when events occur. Picking an alarm instruction to use follows the same decision path as picking an action plan: Command Centre looks at the configuration of the source item first, and finding nothing there will turn to the source item's alarm zone, and finally to the server properties. If all three are unset the operator will not receive any special instructions.
So, what an operator sees when your event arrives on their board depends on the priority and the event source.
When events become alarms
After the server has established an event's priority, either from the body you POSTed or the action plan, it looks at the Event Priorities tab of the server properties. There is a slider there that sets the level above which an event becomes an alarm. By default it is set to two, meaning that any event with a priority of two or higher will appear as an alarm.
Body
You can specify many things on an event but the only mandatory field is the type. When you are developing, start with just that.
typeobjectdeprecatedA new event must contain either this or (if your server is running 8.90 or later) eventType.
Without one of them, the POST will fail.
The event type is mandatory because the server cannot assume a reasonable default, as it does for the other fields.
If you send both a type block and an eventType block to a server running 8.90 or later
it will use eventType. Versions before 8.90 do not know about eventType so they use type.
eventTypeanyrequiredA new event must contain either this or type. Without one of them, the POST will
fail.
The event type is mandatory because the server cannot assume a reasonable default, as it does for the other fields.
If you send both a type block and an eventType block to a server running 8.90 or later
it will use eventType. Versions before 8.90 do not know about eventType so they use type.
sourceobjectThis block should contain the href of the item you wish to use as the source of your
event. It can be any site item to which your operator has view access including all
hardware, access zones, fence zones, doors, lockers, car parks, servers, external systems,
and many other item types. If you do not supply one Command Centre will use the REST
Client item identified by the API key in the Authorization header.
Cardholders cannot be event sources. To relate a cardholder to your event, use the
cardholder block.
Make sure that your operator has the 'Create Events and Alarms' privilege on this item's division. 8.90 and later insist on it.
The API will use the source's division as the event's division.
priorityinteger[1, 9]It is not possible to submit an event with priority zero in the body, but if you submit an event with no priority it will use the one on the event type's action plan, which can be zero.
timestring<date-time>Like all other fields in this POST apart from the type, this field is optional. If you send it, it must be in the format described here.
If you do not send this, the server will use the time that it received your request (its "now").
messagestringThis is the first thing an operator will see when they look at this event. Some interactive clients do not give it a lot of room on screen so put the important parts of your message first. It has a limit of 1024 characters.
detailsstringCommand Centre will attach this string to event, as it does the message, but operators will have to look more closely at the event to see it. On the upside, it can be longer than the message: 2048 characters in 8.10.
cardholderobjectIf you wish to attach a cardholder to your event, link it here. Reports can show or filter by the cardholder.
operatorobjectIf you wish to attach an operator to your event, link it here. Like the cardholder, reports can show or filter by the operator.
entryAccessZoneobjectIf you wish to attach an access zone to your event, link it here. Reports can filter by and show the entry access zone on events.
accessGroupobjectIf you wish to attach an access group to your event, link it here.
Unlike cardholders, operators, and entry access zones, access groups do not appear in Command Centre activity reports. You can add a filter to restrict an activity report by access groups, but the group that allowed an event into the report will not appear in a column.
Like all the other items you link to your event it will, of course, appear when you GET the event from the API later.
lockerBankobjectIf you wish to attach a locker bank to your event, link it here.
Like an event's access group, you can filter a Command Centre activity report to events that involve a locker bank, but the bank will not appear in the report itself.
If you link both a locker and a locker bank to an event, Command Centre does not require that the locker is in the locker bank, but you may find that downstream reporting software misbehaves when it is not.
lockerobjectIf you wish to link a locker to your event, do it here. Like an event's access group and locker bank, you can filter a Command Centre activity report to events that involve a locker, but the locker will not appear in the report itself.
doorobjectIf you wish to link a door to your event for later extraction or a report filter, do it here.
Response
Success.
Success with no event created, probably because the priority in the action plan was zero.
The parameters are invalid. Check the body of the response for an error message.
8.90 and later will reject the POST with a 400 if your event specifies a source item that is not your REST Client item and your operator does not have 'Create Events and Alarms' on its division.
The site does not have the RESTCreateEvents licence (in which case the body of the response will say so) or operator does not have the 'Create Events and Alarms' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/events", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/events") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/events' \
-H 'Content-Type: application/json' \
-d '{
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}'const response = await fetch('https://127.0.0.1:8904/api/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}
response = requests.post('https://127.0.0.1:8904/api/events', json=payload)
data = response.json()This is an example showing how you could create a new event. See the [POST](#path-Events/post/api/events) for where to get the address of the endpoint, the rules around event types and items, and what happens to your event after you create it, and see the [schema definition](#definition-EventPOSTBody) for a description of each field. Only `eventType` is required for the API to accept the POST, but an event is not much without a source item and a message string, and it never hurts to add some details. To make this example work on a server running 8.80 or earlier change `eventType` to `type`.
{
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"message": "Glass break detected in southwest sauna",
"details": "Temperature dropping"
}Get new events (or wait)
Poll this link to receive new events that match the specified filters. If there are none ready, the call will block until one arrives or a deadline passes.
For its correct use in various scenarios, see the use cases. In particular, sleep between calls to reduce load on the server.
The way this call picks its initial set of events is different from /api/events:
If you follow the link at
events.updates.hrefin the results ofGET /api, it will block until at least one event arrives that meet your search criteria, then return them.If you follow the
updateslink from the results ofGET /api/eventsorGET /api/events/updates, the link will contain a bookmark parameter that causes the server to return the first events that arrived after that bookmark (and that meet your search criteria, of course). If there are none, it will block until some arrive that do.
The response will contain an updates link back to the same API call with a new bookmark
that will cause it to return the next page of results.
Each response will also contain a next link that will take you to the non-blocking version
of the call at /api/events. However if you are using this
call you should not also be using the next link: applications typically use one or the
other, not both.
However long you wait between calls, following a next or updates link will always return
the first events that arrived after your previous call.
Do not code this URL into your application. Take it from events.updates.href in the
results of GET /api, or from updates in the results of GET /api/events.
Parameters
topinteger[1, 10000]querySets the maximum number of events to return per page.
deadlineinteger[1, 86400]querySets the number of seconds to wait for an event, if none are ready when you make the call. If none arrive before this number of seconds pass the result set will be empty. If not specified, a default will apply.
afterstring<date-time>queryRemoves events that occurred before this time from the result set. It must be an ISO-8601 date or date-time with a timezone.
It is unlikely you will add this parameter to GET /api/events/updates. However it is a
very useful parameter to /api/events, and it will pass from
there into the updates URL that that call returns. In that case it has no effect; you
can leave it there.
This will not push the start time of the search into the past. As described above, the
search will start at the time of the call or after the last event in a previous result set
depending on the pos parameter. The after parameter reduces the results to those
events that arrived after the start time of the search and occurred after the after
timestamp. Note that an event's arrival time can be different from its occurrence time.
beforestring<date-time>queryRemoves events that occurred at or after this time from the result set. It must be an ISO-8601 date or date-time string with a timezone.
If no events arrive with an occurrence time earlier than this parameter, the call will eventually time out.
It is unlikely you will want this parameter in a call to GET /api/events/updates. It
puts an end-date on the search, which is a very odd thing to do on a call intended to keep
the caller up to date with events as they arrive. If you find yourself using it, you may
wish to reconsider your approach.
If you only wish to receive events up to a point in history, use the before parameter on
/api/events, following its next block in a loop until you
get an empty result. If you only wish to receive events up to a point in the future, use
/api/events again but loop until you receive an event with a date beyond your stopping
point.
sourceArray<string>queryRestricts events to those whose source item has this ID. Separate multiple IDs with
commas. Use /api/items to search Command Centre's items.
typeArray<string>queryRestricts events to those with this event type ID. Separate multiple IDs with commas.
Use the API to see all of Command Centre's event types and groups. Event types names and IDs rarely change, but the ID is the more stable of the two. Therefore it is probably safer to use that API for reference then hard-code the event type IDs you find there into your application.
groupArray<string>queryRestricts events to those with this event group ID. Separate multiple IDs with commas.
The documentation for /api/events/groups advises when to filter for event groups instead
of event types.
Like event types, hard-coding the ID into your application is probably stabler (and definitely simpler) than searching for it at runtime.
cardholderstringqueryRestricts events to those associated with the cardholder with this Command Centre ID. Separate multiple IDs with commas.
Example: cardholder=325
divisionstringqueryRestricts events to those in the division with this ID and its descendant divisions. Separate multiple IDs with commas.
Example: division=2,101
fieldsstringdefaultsdetailscardholder.pdf_*hrefidserverDisplayNametimemessageoccurrencespriorityalarmoperatorsourcegrouptypeeventTypedivisioncardholderentryAccessZoneexitAccessZonedooraccessGroupcardmodifiedItemlastOccurrenceTimepreviousnextupdateslocationqueryIn 8.40 and later the values you can list are the same as the field names in the details
page, plus a special field name for a personal data field,
described next. You can pick whichever fields you want, including defaults though we urge
you to avoid that and only ask for the fields you need.
You can request cardholder.pdf_XXXX where XXXX is the ID of a PDF, which you can discover
with a query to the PDFs
controller. That will add a
cardholder's PDF to the events that are related to them.
You can only pick one personal data field. It will only appear on events that have a related cardholder, such as access events, because without a cardholder there is no personal data. The security model applies too, so it will only appear if your REST operator has the appropriate privileges on that cardholder and PDF.
posinteger>= 0queryINTERNAL USE ONLY. Retain the 'next' or 'updates' link in your application instead. This is how Command Centre tracks the events you have seen already. Do not set it yourself.
Response
Success
The site does not have a RESTEvents licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/events/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/events/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/events/updates'const response = await fetch('https://127.0.0.1:8904/api/events/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/events/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/events/updates')
data = response.json(){
"events": [
{
"href": "https://localhost:8904/api/events/61320",
"id": "61320",
"serverDisplayName": "ruatoria.satellite.int",
"time": "2016-02-18T19:21:52Z",
"message": "Operator logon failed for FT Workstation on GNZ-PC1439",
"occurrences": 2,
"priority": 3,
"alarm": {
"state": "unacknowledged",
"href": "https://localhost:8904/api/alarms/61320"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Chong, Marc"
},
"source": {
"id": "321",
"name": "FT Workstation on GNZ-PC1439",
"href": "https://localhost:8904/api/items/321"
},
"group": {
"id": "35",
"name": "Invalid Logon"
},
"type": {
"id": "601",
"name": "Operator logon failed"
},
"eventType": {
"id": "601",
"name": "Operator logon failed"
},
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2",
"name": "Root division"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"id": "325",
"name": "Bruce, Jennifer",
"firstName": "Jennifer",
"lastName": "Caitlin"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Brookwood showroom",
"id": "333"
},
"exitAccessZone": {
"href": "https://localhost:8904/api/access_zones/913",
"name": "Compressor room",
"id": "913"
},
"door": {
"href": "https://localhost:8904/api/doors/745",
"name": "Main hoist door"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"card": {
"facilityCode": "A12345",
"number": "78745",
"issueLevel": 1
},
"modifiedItem": {
"href": "https://localhost:8904/api/cardholders/325",
"type": {
"id": "1",
"name": "Cardholder"
}
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"previous": {
"href": "https://localhost:8904/api/events?pos=61320&previous=True"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}
],
"previous": {
"href": "https://localhost:8904/api/events?previous=True&pos=61320"
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}Get details of an event
Full details for an event. You could follow the href in the event summary to get here, but
if you are running 8.20 or later you could just use the fields parameter to add the
details field to the summary results for the same result.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringdefaultsdetailscardholder.pdf_*hrefidserverDisplayNametimemessageoccurrencespriorityalarmoperatorsourcegrouptypeeventTypedivisioncardholderentryAccessZoneexitAccessZonedooraccessGroupcardmodifiedItemlastOccurrenceTimepreviousnextupdateslocationqueryIn 8.40 and later the values you can list are the same as the field names in the details
page, plus a special field name for a personal data field,
described next. You can pick whichever fields you want, including defaults though we urge
you to avoid that and only ask for the fields you need.
You can request cardholder.pdf_XXXX where XXXX is the ID of a PDF, which you can discover
with a query to the PDFs
controller. That will add a
cardholder's PDF to the events that are related to them.
You can only pick one personal data field. It will only appear on events that have a related cardholder, such as access events, because without a cardholder there is no personal data. The security model applies too, so it will only appear if your REST operator has the appropriate privileges on that cardholder and PDF.
Response
Success
The site does not have a RESTEvents licence.
That is not the URL of an event, or it is but you do not have privileges to read events in its division.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/events/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/events/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/events/{id}'const response = await fetch('https://127.0.0.1:8904/api/events/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/events/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/events/{id}')
data = response.json(){
"href": "https://localhost:8904/api/events/61320",
"id": "61320",
"serverDisplayName": "ruatoria.satellite.int",
"time": "2016-02-18T19:21:52Z",
"message": "Operator logon failed for FT Workstation on GNZ-PC1439",
"occurrences": 2,
"priority": 3,
"alarm": {
"state": "unacknowledged",
"href": "https://localhost:8904/api/alarms/61320"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Chong, Marc"
},
"source": {
"id": "321",
"name": "FT Workstation on GNZ-PC1439",
"href": "https://localhost:8904/api/items/321"
},
"group": {
"id": "35",
"name": "Invalid Logon"
},
"type": {
"id": "601",
"name": "Operator logon failed"
},
"eventType": {
"id": "601",
"name": "Operator logon failed"
},
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2",
"name": "Root division"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"id": "325",
"name": "Bruce, Jennifer",
"firstName": "Jennifer",
"lastName": "Caitlin"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Brookwood showroom",
"id": "333"
},
"exitAccessZone": {
"href": "https://localhost:8904/api/access_zones/913",
"name": "Compressor room",
"id": "913"
},
"door": {
"href": "https://localhost:8904/api/doors/745",
"name": "Main hoist door"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"card": {
"facilityCode": "A12345",
"number": "78745",
"issueLevel": 1
},
"modifiedItem": {
"href": "https://localhost:8904/api/cardholders/325",
"type": {
"id": "1",
"name": "Cardholder"
}
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"previous": {
"href": "https://localhost:8904/api/events?pos=61320&previous=True"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
},
"lastOccurrenceTime": "2016-02-18T19:21:59Z",
"details": "Originating IP address: 192.168.2.3",
"location": {
"type": "moved",
"cardholder": {
"name": "Jackson",
"href": "https://localhost:8904/cardholders/325"
},
"beforeLocation": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Lvl 1 lift lobby",
"canonicalTypeName": "accesszone"
},
"afterLocation": {
"outside": true
}
}
}List event types
Retrieves the list of event type groups and the event types within those groups. Useful for obtaining IDs to use in event filters. Command Centre ships with about 1000 event types divided into about 150 groups. Each event type is in one group.
A site may rename the 30 groups dedicated to external event types—that is, event types for the site's own use—and may create another 970 event types for them.
The results of this query vary with:
- the server version,
- extra Gallagher software installed on the site, and
- changes to external event groups and types made by the customer.
Event type identifiers do not often change between Command Centre versions, but the types in each group do. Therefore if you choose to use groups in filters rather than types you may find that your filter catches more or fewer event types after a Command Centre upgrade. That may be desirable if, for example, you are intested in a class of event and you want to grow with CC as it grows new features. If you want to catch all 'access denied's, for example. We frequently add new varieties of 'access denied'. If you do not want that, use types rather than groups.
Do not code this URL into your application. Take it from events.eventGroups.href in the
results of GET /api.
Response
Success
The site does not have the RESTEvents or RESTCreateEvents licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/events/groups", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/events/groups"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/events/groups'const response = await fetch('https://127.0.0.1:8904/api/events/groups', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/events/groups', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/events/groups')
data = response.json(){
"eventGroups": [
{
"id": "35",
"name": "Invalid Logon",
"eventTypes": [
{
"href": "https://localhost:8904/api/events/types/601",
"id": "601",
"name": "Operator logon failed"
},
{
"href": "https://localhost:8904/api/events/types/20065",
"id": "20065",
"name": "Terminal: Invalid User Code"
},
{
"href": "https://localhost:8904/api/events/types/23052",
"id": "23052",
"name": "Wrong Code only Code"
}
]
}
]
}Fence Zones
These methods give you read access to Fence Zones in the Command Centre database, letting you monitor their status, turn them on and off, change their modes, and shunt them.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of fence zones, each containing the fields you ask for in the query.
Fence Zone status flags
If the fence zone is online, its statusFlags field may contain one or more of these flags:
notPolledmeans the fence zone is shunted, which means intentionally ignored, and is essentially offline.overriddenmeans just that.deterrentUnknownmeans the controller cannot determine the state of the fence zone. This happens when there is a cabling problem between the C6000 and the fence controller (energiser).onmeans the fence zone is live, energised. Take care.offmeans the fence zone is off.lowFeelmeans the fence energiser is delivering enough voltage to detect disturbances. Depending on the fence zone's configuration, it may also be a deterrant.highVoltagemeans the pulse is delivering a deterrant pulse.hVPlusModemeans the fence voltage has increased in response to a disturbance. After some time or an override it will leave this mode. See the description of HVPlus mode in the Configuration Client's online help.serviceModemeans a technician has manually forced service mode at the fence controller. This safety measure prevents a Command Centre operator in an operations room inadvertently energising a hardware technician out in the field.voltageKnownmeans Command Centre has the current voltage for the zone, provided there has been at least one pulse from the energiser recently. This can only happen when the zone is on, obviously. The 'voltage' field will contain the voltage at the last pulse.alertmeans the fence voltage is outside the alert range. Typically this means an electical problem.warningmeans the fence voltage is between the alert and warning ranges. Typically this means it has grounded somewhere.preArmmeans the zone is performing its pre-arm check.lockedOutmeans the zone has been locked out at a keypad.parentAlertmeans there is a problem with the fence controller.
The item status section describes the flags an item returns when it is not online.
Fence Zone flag rules
- If and only if the fence zone is online, there will be exactly one of 'deterrentUnknown', 'on', or 'off'. That is your test for whether a fence zone is in error.
- If 'on', there will be exactly one of 'lowFeel', 'highVoltage', or 'hVPlusMode'.
- If 'off', there will be exactly one of 'lowFeel' or 'highVoltage'.
- 'hVPlusMode' will only appear if the fence zone is on.
- If there is 'alert' there will never be 'warning'.
- None of the flags above will appear if the fence zone is offline and only 'notPolled' will appear if it is not polled.
- The 'voltage' field only contains a valid value if you receive 'voltageKnown' and the energiser has pulsed at least once since the zone turned on.
Use cases
Searching for fence zones by name
GET /api.- Follow the link at
features.fenceZones.fenceZones.href↪, appending a search term such asname=substringto filter the fence zones, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink until there isn't one.
Overriding a fence zone
- Find the href for the fence zone using the process above.
- GET it.
- Find the API URL you require in the
commandsstructure of the results, such asoff,highVoltage, orshunt, from the detail. - POST to that URL. You do not need to send anything in the body of the POST.
Finding a fence zone's status
- Find the href for the fence zone using the process above, and GET it.
- Take the
updates↪ href from that page. If you are after the fence's voltage and are using version 8.00, appendfields=defaults,voltage(after a?or&). You do not need that for later versions asvoltagebecame a default field in 8.10. - GET it.
- Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search fence zones
This returns a summary of the fence zones matching your search criteria.
The result will contain no more than 100 or 1000 fence zones (depending on your version), or
as many as you asked for more in your request; you should follow the next link, if it is
present, to collect the next batch.
If your result set is empty it means your operator does not have a privilege that allows viewing fence zones, such as 'View Site', 'Edit Site', or 'Maintenance Override'. Perhaps there are no fence zones in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.fenceZones.fenceZones section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllervoltagestatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/fence_zones", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/fence_zones"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/fence_zones'const response = await fetch('https://127.0.0.1:8904/api/fence_zones', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/fence_zones')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/fence_zones/8487",
"id": "8487",
"name": "Storage yard"
}
],
"next": {
"href": "https://localhost:8904/api/fence_zones?skip=1000"
}
}Get details of a fence zone
This returns the detail of one fence zone.
Follow the 'href' field in a fence zone summary to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllervoltagestatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences.
The request's URL does not represent a fence zone, or the operator does not have a privilege on the zone's division that allows viewing fence zones, such as 'View Site', 'Edit Site', or 'Maintenance Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/fence_zones/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/fence_zones/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/fence_zones/{id}'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/fence_zones/{id}')
data = response.json(){
"href": "https://localhost:8904/api/fence_zones/8487",
"id": "8487",
"name": "Storage yard",
"description": "Trailers and pallets.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"voltage": 7300,
"notes": "Multi-line text...",
"shortName": "Short text",
"updates": {
"href": "https://localhost:8904/api/fence_zones/8487/updates/0_0_0"
},
"statusFlags": [
"on",
"highVoltage",
"voltageKnown"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"on": {
"href": "https://localhost:8904/api/fence_zones/8487/on"
},
"off": {
"href": "https://localhost:8904/api/fence_zones/8487/on"
},
"shunt": {
"href": "https://localhost:8904/api/fence_zones/8487/shunt"
},
"unshunt": {
"href": "https://localhost:8904/api/fence_zones/8487/unshunt"
},
"highVoltage": {
"href": "https://localhost:8904/api/fence_zones/8487/high_voltage"
},
"lowFeel": {
"href": "https://localhost:8904/api/fence_zones/8487/low_feel"
},
"cancel": {
"href": "https://localhost:8904/api/fence_zones/8487/cancel"
}
}
}Turn on a fence zone
Sends an override to an alarm zone to turn it on until the next scheduled or manual change.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/on", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/on"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/on'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/on', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/on', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/on')
data = response.json()Turn off a fence zone
Sends an override to an alarm zone to turn it off until the next scheduled or manual change.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/off", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/off"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/off'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/off', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/off', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/off')
data = response.json()Shunt a fence zone
Sends an override to an alarm zone to shunt it, effectively preventing all communication with it.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/shunt", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/shunt"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/shunt'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/shunt', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/shunt', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/shunt')
data = response.json()Unshunt a fence zone
Sends an override to an alarm zone to unshunt it, re-enabling its communication.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/unshunt", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/unshunt"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/unshunt'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/unshunt', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/unshunt', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/unshunt')
data = response.json()Change to high voltage
Sends an override to an alarm zone to change it to high voltage mode until the next scheduled or manual change.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage')
data = response.json()Change to low feel
Sends an override to an alarm zone to change it to 'low feel' mode until the next scheduled or manual change.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/low_feel", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/low_feel"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/low_feel'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/low_feel', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/low_feel', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/low_feel')
data = response.json()Cancel an override
Cancels an active override.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding fence zones (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/fence_zones/{id}/cancel", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/fence_zones/{id}/cancel"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/fence_zones/{id}/cancel'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/cancel', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/cancel', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/fence_zones/{id}/cancel')
data = response.json()Monitor a fence zone
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in a door summary or details pages to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsanystatusstatusTextstatusFlagsvoltagequeryThis instructs the server to return these fields in the update, instead of the default set. You will not hear about updates to fields you do not list.
Response
Success. See the introduction for a description of the three status
fields and the detail page for voltage.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent a fence zone, or the operator does not have a privilege on the zone's division that allows viewing fence zones, such as 'View Site', 'Edit Site', or 'Maintenance Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/fence_zones/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/fence_zones/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/fence_zones/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/fence_zones/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/fence_zones/{id}/updates')
data = response.json(){
"updates": {
"status": "On - HV.",
"statusText": "On - HV.",
"statusFlags": [
true,
"highVoltage"
],
"voltage": 7300
},
"next": {
"href": "https://localhost:8904/api/fence_zones/556/updates/9_1"
}
}Inputs
These methods, introduced in v8.10, give you read and override access to Input items.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of inputs, each containing the fields you ask for in the query. You can, for instance, ask for the URLs you need to shunt or isolate an input.
Input status flags
Even though the Configuration client lets you set your own state names for open, closed, tampered open, and tampered short, status flags will always use 'closed' or 'open', and possibly 'tamper'.
Two-state inputs cannot report a tamper, because it is impossible to detect. They can only be open or closed. Hence the name.
Three-state inputs can report a tamper, but only one of tampered open or tampered closed, depending on their end-of-line resistance settings.
Four-state inputs can report tampered open (an open circuit) or tampered closed (a short).
If the input is online, its statusFlags field may contain one or more of these flags:
closedmeans the input circuit is closed.openmeans the input circuit is open.tampermeans the input circuit is shorted or open.notPolledmeans the input is shunted, which means intentionally ignored, and is essentially offline.isolatedmeans the input state will not prevent arming an alarm zone.
Input flag rules
If and only if the input is online and not shunted ('notPolled' status flag), exactly one of 'closed' or 'open' will appear.
'closed' or 'open' may still appear if the input is tampered.
So, to establish if the input is in a completely normal state, look for 'closed' or 'open', and make sure 'tamper' is not there. However bear in mind that inputs are often shunted for ordinary reasons.
Use cases
Listing Inputs
GET /api.- Follow the link at
features.inputs.inputs.href, appending a search term such asname=substringto select the inputs,topif you expect lots of them, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink if there is one.
Overriding an Input
- Find the href for the input using the process above.
- GET it.
- Find the API URL for the override you need in the
commandsstructure of the results. - POST to that URL with an empty body.
Finding an input's status
- Find the href for the input using the process above, and GET it.
- Take the
updates↪ href from that page. - GET it.
- Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search inputs
This returns a summary of the inputs matching your search criteria.
The result will contain no more than 100 or 1000 inputs (depending on your version), or as
many as you asked for more in your request; you should follow the next link, if it is
present, to collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any inputs, such as 'View Site', 'Edit Site', or 'Maintenance Override'. Perhaps there are no inputs in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.inputs.inputs section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/inputs", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/inputs"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/inputs'const response = await fetch('https://127.0.0.1:8904/api/inputs', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/inputs')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/inputs/9701",
"id": "9701",
"name": "Studio door open sensor"
}
],
"next": {
"href": "https://localhost:8904/api/inputs?skip=1000"
}
}Get details of an input
This returns the detail of one input.
Follow the 'href' field in an input summary to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an input, or the operator does not have a privilege on the input's division that allows viewing inputs, such as 'View Site', 'Edit Site', or 'Maintenance Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/inputs/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/inputs/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/inputs/{id}'const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/inputs/{id}')
data = response.json(){
"href": "https://localhost:8904/api/inputs/9701",
"id": "9701",
"name": "Studio door open sensor",
"description": "Reed switch.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/inputs/9701/updates/0_0_0"
},
"statusFlags": [
"open"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"shunt": {
"href": "https://localhost:8904/api/inputs/9701/shunt"
},
"unshunt": {
"href": "https://localhost:8904/api/inputs/9701/unshunt"
},
"isolate": {
"href": "https://localhost:8904/api/inputs/9701/isolate"
},
"deisolate": {
"href": "https://localhost:8904/api/inputs/9701/deisolate"
}
}
}Shunt an input
Sends an override to shunt an input, preventing all communication.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have the 'Maintenance Override' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/inputs/{id}/shunt", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/inputs/{id}/shunt"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/inputs/{id}/shunt'const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/shunt', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/shunt', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/inputs/{id}/shunt')
data = response.json()Unshunt an input
Sends an override to unshunt an input, re-enabling communication.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have the 'Maintenance Override' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/inputs/{id}/unshunt", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/inputs/{id}/unshunt"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/inputs/{id}/unshunt'const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/unshunt', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/unshunt', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/inputs/{id}/unshunt')
data = response.json()Isolate an input
Sends an override to isolate an input. An isolated input will not prevent an alarm zone from arming.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have the 'Maintenance Override' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/inputs/{id}/isolate", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/inputs/{id}/isolate"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/inputs/{id}/isolate'const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/isolate', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/isolate', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/inputs/{id}/isolate')
data = response.json()De-isolate an input
Sends an override to end the isolation of an input.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have the 'Maintenance Override' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/inputs/{id}/deisolate", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/inputs/{id}/deisolate"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/inputs/{id}/deisolate'const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/deisolate', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/deisolate', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/inputs/{id}/deisolate')
data = response.json()Monitor an input
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in an input summary or details pages to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringstatusstatusTextstatusFlagsqueryThis instructs the server to return these fields in the update, instead of the default set. You will not hear about updates to fields you do not list.
Response
Success. See the introduction for a description of the three status fields.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an input, or the operator does not have a privilege on the input's division that allows viewing inputs, such as 'View Site', 'Edit Site', or 'Maintenance Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/inputs/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/inputs/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/inputs/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/inputs/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/inputs/{id}/updates')
data = response.json(){
"updates": {
"status": "This Input is Open-Circuit Tampered.",
"statusText": "This Input is Open-Circuit Tampered.",
"statusFlags": [
"open",
"tamper"
]
},
"next": {
"href": "https://localhost:8904/api/inputs/2365/updates/9_1"
}
}Interlock Groups
These methods, yet to be released, will give you read access to Interlock Group items.
API support for interlocks is still in development and may change in future versions.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of interlock groups, each containing the fields you ask for in the query. You can, for instance, ask for the IDs you need to monitor their status.
Interlock Group status flags
If an interlock is not in an error state it will return one flag out of the following set:
securemeans the interlock items are closed and the door/s will open to a badge. This is probably where an interlock group spends most of its time.openmeans the interlock's doors will not open because at least one of the other items in the group already is. This is a normal state after someone gains access at an interlock door. It will last until the door closes.overriddenmeans the interlock group has received a disable override. Doors will open as normal.forcedmeans that the interlock rules have been breached. This could be because someone forced a door, or used an emergency release while another door was already open.
If an interlock is completely normal it will report 'secure' or 'open'.
Use cases
Listing Interlock Groups
GET /api.- Follow the link at
features.interlockGroups.interlockGroups.href, appending a search term such asname=substringto select the interlocks,topif you expect lots of them, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink if there is one.
Finding the status of many items including an interlock group
- Find the IDs of all the items you're interested in, including the interlock group, by
searching for them with a query parameter appended such as
fields=name,id. - Create a status subscription for those items.
Finding the status of an interlock group
- Find the href for the interlock using the process above, and GET it.
- Take the
updateshref from that page. - GET it.
- Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Overriding an Interlock Group
- Find the href for the interlock using the process above.
- GET it.
- Find the API URL for the override you need (disable or re-enable) in the
commandsstructure of the results. - POST to that URL with an empty body.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search interlock groups
Not yet available. API support for interlocks is still in development and may change in future versions.
This returns a summary of the interlock groups matching your search criteria.
The result will contain no more than 100 or 1000 interlock groups (depending on your
version), or as many as you asked for more in your request; you should follow the next
link, if it is present, to collect the next batch.
If your result set is empty it means your operator does not have a privilege that allows viewing interlock groups, such as 'View Site', 'Edit Site', or 'Maintenance Override'. Perhaps there are no interlock groups in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.interlockGroups.interlockGroups section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The site has neither the RESTStatus nor the RESTOverrides licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/interlock_groups", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/interlock_groups"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/interlock_groups'const response = await fetch('https://127.0.0.1:8904/api/interlock_groups', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/interlock_groups', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/interlock_groups')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/interlock_group/122322",
"name": "Excercise yard egress"
}
],
"next": {
"href": "https://localhost:8904/api/interlock_groups?skip=1000"
}
}Get details of an interlock group
Not yet available. API support for interlocks is still in development and may change in future versions.
This returns the detail of one interlock group.
Follow the 'href' field in an interlock group summary to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
Response
Success.
The site has neither the RESTStatus nor the RESTOverrides licence.
The request's URL does not represent an interlock or the operator does not have a privilege on the interlock's division that allows viewing interlocks, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/interlock_groups/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/interlock_groups/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/interlock_groups/{id}'const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/interlock_groups/{id}')
data = response.json(){
"href": "https://localhost:8904/api/interlock_group/122322",
"name": "Excercise yard egress",
"id": "122322",
"description": "Exercise yard egress.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/interlock_groups/122322/updates/0_0_0"
},
"statusFlags": [
"secure"
],
"statusText": "All doors in the group are in a Secure state.",
"status": "All doors in the group are in a Secure state.",
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"disable": {
"href": "https://localhost:8904/api/interlock_groups/122322/disable"
},
"enable": {
"href": "https://localhost:8904/api/interlock_groups/122322/enable"
}
}
}Disable an interlock group.
Not yet available. API support for interlocks is still in development and may change in future versions.
Sends an override to disable an interlock group, allowing all doors to act independently.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have the 'Override' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/interlock_groups/{id}/disable", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/interlock_groups/{id}/disable"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/interlock_groups/{id}/disable'const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}/disable', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}/disable', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/interlock_groups/{id}/disable')
data = response.json()Re-enable an interlock group.
Not yet available. API support for interlocks is still in development and may change in future versions.
Cancels the disabling override on an interlock group, causing the doors to return to interlocking behaviour.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have the 'Override' privilege.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/interlock_groups/{id}/enable", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/interlock_groups/{id}/enable"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/interlock_groups/{id}/enable'const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}/enable', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}/enable', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/interlock_groups/{id}/enable')
data = response.json()Monitor an interlock group.
Not yet available. API support for interlocks is still in development and may change in future versions.
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in an interlock group summary or details pages to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringstatusstatusTextstatusFlagsqueryThis instructs the server to return these fields in the update, instead of the default set. You will not hear about updates to fields you do not list.
Response
Success. See the introduction for a description of the status fields.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an interlock or the operator does not have a privilege on the interlock's division that allows viewing interlocks, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/interlock_groups/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/interlock_groups/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/interlock_groups/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/interlock_groups/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/interlock_groups/{id}/updates')
data = response.json(){
"updates": {
"statusFlags": [
"open"
]
},
"next": {
"href": "https://localhost:8904/api/interlock_groups/122322/updates/9_1"
}
}Items
These methods let you find items for your search filters and events, and monitor their states. They return all items, including those that this API does not yet support in depth, and items added by customisations. However because they do not have deep knowledge of item types they can only give you the most basic fields.
Searching for items
Use the search methods when you are building a filter for an event search or a status subscription and need the ID of an item or an item type, or when you are creating an event and need an href to use as the event source.
To find an item, pass a substring of its name to the link at features.items.items.href in the
results of a call to /api. If you are sure of its name, place the name inside " quotes, and
it will use a full string match. Both types of search are case-insensitive.
To limit the search to items of a particular type, first get the ID of the type you
are after using the link at features.items.itemTypes.href in the results of GET /api.
Add that ID as the type parameter to the call above. You can specify multiple item
types if, for example, you are interested in all the different kinds of doors.
For example, if you were after a list of divisions, following the instructions above on the
current versions of Command Centre would produce the URL /api/items?type=15.
Status subscriptions
The item-specific APIs monitor only one item at a time, and are therefore not suitable for watching large collections. If you have CC version 8.30 or later, you should use the status subscription methods instead. We have tested subscriptions of 1000 items without noticing undue strain on the server. While the calls do not impose an upper bound on that we suggest keeping a watchful eye on the performance of the overall system if you go much higher.
The basic operations for monitoring item states is:
Get all the IDs of the items you wish to monitor. You can do that using this API's own search with a
name,division, ortypeparameter. If you're searching by division you'll need a division ID which (slightly recursively) is best found by using the same search function filtering for just divisions. If you're searching by type you'll need a type ID.POST to create a subscription. Your program should get the URL from
items.updates.hrefin the results ofGET /api.Take the current state of your items from the results of that call. If that's all you need, terrific. But if you want to monitor their state, continue.
GET the
next.hreflink that came in the results. The call will block until one of your monitored items changes state. When it returns, the results will be in the same format as the result of the POST, including thenextlink.Goto 4.
Licensing
Every REST licence enables the items controller: RESTEvents, RESTCreateEvents, RESTCardholders, RESTStatus, and RESTOverrides.
Search items
This returns a batch of items matching the applied filters. By default, each page will
contain up to 1000 items although this can be changed by setting the top
parameter in the request URL.
You will only receive items for which the REST operator has the necessary privilege. To view PDFs, for example, the operator must have the 'View Personal Data Definitions' privilege.
If more items are available, the response will contain a next link. Following
that will return the next batch of items.
Items will be in ID order unless you change it with sort.
Do not code this URL into your application. Take it from items.items.href in the results
of GET /api.
Parameters
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
typestringqueryOnly returns items that are of a type with this ID. In versions up to 8.30 you could only specify one, but in 8.40 and later this can be a comma-separated list.
Note that the server will ignore invalid IDs, and if they are all invalid it will act as though the filter was absent and return all items. This is a bug. Future versions will return nothing if all the type IDs are invalid.
topinteger>= 1100querySets the maximum number of items to return per page.
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
fieldsstringhrefidnametypedivisionserverDisplayNamenotesdescriptionshortNamequeryReturn these fields in the search results. The values you can list are the same as the field names in the details page. Using it you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
The string must not contain any spaces. Just alphanumerics, underscores, commas, and dots.
description and shortName arrived in 9.60.
Treat the string matches as case-sensitive.
Response
Success
The site does not have a REST licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/items", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/items"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/items'const response = await fetch('https://127.0.0.1:8904/api/items', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/items', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/items')
data = response.json(){
"results": [
{
"id": "325",
"name": "Brick, Eva",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
}
},
{
"id": "2707",
"name": "Brewer, Amy",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
}
}
],
"next": {
"href": "https://localhost:8904/api/items?pos=2"
}
}Get details of an item
This returns some basic fields for one item. It returns the same information as the item search plus the item's division.
Added in 8.40.
Parameters
idstringrequiredpathThe ID of the item.
fieldsstringhrefidnametypedivisionserverDisplayNamenotesdescriptionshortNamequeryReturn these fields in the search results. The values you can list are the same as the field names in the details page. Using it you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
The string must not contain any spaces. Just alphanumerics, underscores, commas, and dots.
description and shortName arrived in 9.60.
Treat the string matches as case-sensitive.
Response
Success
That is not the href of an item. At least not one that your operator has the privilege to view.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/items/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/items/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/items/{id}'const response = await fetch('https://127.0.0.1:8904/api/items/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/items/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/items/{id}')
data = response.json(){
"id": "325",
"name": "Brick, Eva",
"shortName": "Short text",
"description": "string",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
},
"serverDisplayName": "ruatoria.satellite.int",
"notes": "Multi-line text...",
"href": "string",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
}List item types
Retrieves the list of all item types in the Command Centre system. There are about 200. This is useful for obtaining type IDs to use in item search filters and (in 9.00) the canonical item type names.
Note that some item types have a blank name. These types are vestigial: disregard them.
Do not code this URL into your application. Take it from items.itemTypes.href in the
results of GET /api.
Response
Success
The site does not have a REST licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/items/types", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/items/types"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/items/types'const response = await fetch('https://127.0.0.1:8904/api/items/types', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/items/types', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/items/types')
data = response.json(){
"itemTypes": [
{
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
},
{
"id": "2",
"name": "Access Group",
"canonicalTypeName": "accessgroup"
}
]
}Retrieve status updates
Collects status updates from a subscription created using the /api/items/updates POST.
This is a long poll, so if there are no updates waiting when you make the call it will block until some arrive or a timeout passes (about 50 seconds).
If you receive a 404 from this call it means that more than 30 seconds passed between the server sending you the link and you GETting it, or the server restarted. In either case it will have dropped your subscription: you will need to create a new one with a fresh POST.
Therefore your loop can be:
- Create a subscription with a POST.
- Process the statuses in the results, if there are any.
- Wait a second or two to avoid tight loops.
- GET the link from the results. It may take up to a minute to respond.
- If 404, go to 1.
- Go to 2.
...plus the necessary exception handling, of course.
Added in 8.30.
Note that the first time you make this GET request it will return all the activity that came back from the POST. There are other cases where the GET might return no updates or updates you have seen already (when some aspect of an item changes that your operator does not have the privilege to view, or an override is sent, for example). While these two behaviours are not harmful they are also not particularly helpful, so future versions may differ.
Parameters
bookmarkstringrequiredqueryIdentifies your subscription and your position in the change list. You should not to set this parameter: it will be in the link that the server sends back to you.
Response
Success
The site does not have a REST licence.
The subscription does not exist, which probably means you waited too long between calls.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/items/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/items/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/items/updates'const response = await fetch('https://127.0.0.1:8904/api/items/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/items/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/items/updates')
data = response.json(){
"updates": [
{
"id": "508",
"status": "Controller offline. 62 message(s) pending.",
"statusText": "Controller offline.\n62 message(s) pending.",
"statusFlags": [
"controllerOffline"
]
},
{
"id": "526",
"status": "Disarmed.",
"statusText": "Disarmed.",
"statusFlags": [
"disarmed"
]
}
],
"next": {
"href": "https://localhost:8904/api/items/updates?bookmark=3ec613a1-de01c6e_0"
}
}Subscribe to status updates
Creates a subscription to status changes. You POST a list of item IDs and the server returns the status flags of those items plus a link. When you GET that link some time later the server will return the items that changed state between the two calls.
If you do not GET the link within thirty seconds of the POST returning, the server will drop your subscription and free up the resources it had allocated to servicing it. If that happens you will need to send this POST again to create a new subscription.
Your operator must have view privileges on every item in the subscription otherwise you will receive a 4xx.
The subscription notices changes in state, not in configuration, so an operator modifying an item will not cause anything to come out of this API unless the change in configuration also causes a change in state.
Because this call returns the status of items, this call requires the RESTStatus licence.
Added in 8.30. 9.30 corrected the 401 response to a 403.
Body
The body of the POST needs to contain a list of item IDs in an array called itemIds.
Even though they look like small integers, these IDs are actually strings so don't
forget the quotes.
Contains a list of item IDs (short alphanums). Send it in the body of a POST to create a subscription to status updates to the items with these IDs.
itemIdsArray<string>requiredResponse
Success
The server could not parse the request body. Check your JSON.
In versions prior to 9.30 it means the operator does not have privilege to view the monitored items.
The site does not have the RESTStatus licence or, in 9.30 and later, the operator does not have view privileges on all the monitored items.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"itemIds": [
"508",
"526"
]
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/items/updates", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"itemIds": [
"508",
"526"
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/items/updates") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/items/updates' \
-H 'Content-Type: application/json' \
-d '{
"itemIds": [
"508",
"526"
]
}'const response = await fetch('https://127.0.0.1:8904/api/items/updates', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"itemIds": [
"508",
"526"
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/items/updates', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"itemIds": [
"508",
"526"
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"itemIds": [
"508",
"526"
]
}
response = requests.post('https://127.0.0.1:8904/api/items/updates', json=payload)
data = response.json(){
"itemIds": [
"508",
"526"
]
}{
"updates": [
{
"id": "508",
"status": "Controller offline. 62 message(s) pending.",
"statusText": "Controller offline.\n62 message(s) pending.",
"statusFlags": [
"controllerOffline"
]
},
{
"id": "526",
"status": "Disarmed.",
"statusText": "Disarmed.",
"statusFlags": [
"disarmed"
]
}
],
"next": {
"href": "https://localhost:8904/api/items/updates?bookmark=3ec613a1-de01c6e_0"
}
}REST Clients
This lets you operate on the REST Client item that represents your application in Command Centre. You can send a description of the state of your application and raise an alarm if it is in strife.
This is still in development and may change behaviour before release.
REST Client status flags
offlinemeans the item is configured with a keepalive time and it has not seen an API request in that time.faultymeans the item has used the endpoint in this section to declare itself in trouble.
Because this feature is intended for REST integrations needing to indicate their status, this API is the only way to change a REST Client item's status fields. Humans using Gallagher's operational and configuration clients can see it but not change it.
Set status
This allows an integration to add a string to the status text of the REST Client item that represents your application. That text is visible on a suitably-configured site plan or a Monitor Site viewer in Gallagher's operational client, or any other client that is watching items.
Your integration can also set a flag indicating that it is in a fault condition and requires attention. Doing that will raise an alarm, hopefully provoking a response from an operator. The alarm is stateful, restored only by your integration clearing the flag later when it is back in working order.
A client can change its own status, but it can change no other's, and no other client can change its.
Your client's operator does not need any privileges for this call.
Do not code this URL into your application. Take it from me.client.href in the results of
GET /api.
Body
The status text and fault condition of your API client.
Both fields are optional.
hasFaultbooleanIf true, the server will change the status of this REST Client to include an indication that is has a fault, and will raise an active alarm to draw the operators' attention. Being active, operators will not be able to process that alarm until you restore it by passing false here.
customStatusTextstringThe server will include the text you supply here whenever it generates status text for this item.
This text field works independently of the item's other states and its fault flag: you can use it purely for information.
The integrator is responsibile for any personally identifiable information sent to Command Centre via the API.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. The server has feedback for you in the body of its response.
Success.
The server could not make sense of your payload. Its response will contain feedback.
You PATCHed the URL of a REST Client item, but not your REST Client item.
That is not the URL of a REST Client item your operator can see.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"hasFault": false,
"customStatusText": "Disk 87% full"
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/items/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"hasFault": false,
"customStatusText": "Disk 87% full"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/items/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/items/{id}' \
-H 'Content-Type: application/json' \
-d '{
"hasFault": false,
"customStatusText": "Disk 87% full"
}'const response = await fetch('https://127.0.0.1:8904/api/items/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"hasFault": false,
"customStatusText": "Disk 87% full"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/items/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"hasFault": false,
"customStatusText": "Disk 87% full"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"hasFault": False,
"customStatusText": "Disk 87% full"
}
response = requests.patch('https://127.0.0.1:8904/api/items/{id}', json=payload)
data = response.json(){
"hasFault": false,
"customStatusText": "Disk 87% full"
}Lockers
The locker bank API exists for two purposes: finding a locker to assign a cardholder to, and integrating with operational locker management software that expects a bank-centric view rather than the cardholder-centric view available in the cardholder API. So a locker bank's detail page shows all its cardholder assignments.
You cannot use the API to create or configure lockers or banks, or to see the hardware features of a locker such as readers, inputs, and outputs, nor the door-like settings on the bank such as the lock type and unlock time. The locker configuration tool exists for this purpose.
To change the cardholder assignments you should PATCH the cardholder's href, since it is the cardholder you are updating, not the locker. The locker bank API supplies the URL to PATCH.
Locker status flags
Regardless of whether a locker is online or not, its status may contain these:
quarantinedmeans the locked cannot be allocated to a cardholder. It needs a clean, in other words.allocatedmeans it is allocated to a cardholder, so another cardholder cannot self-allocate it.freemeans it is not allocated to a cardholder.
If the locker is online, its statusFlags field may contain one or more of these flags:
forced,openTooLong,tamper,closed,open,locked,unlocked, the same as a door.
Locker status flag rules
A locker can only be one of
quarantined,allocated, orfree.Like a door, if and only if the locker is online, exactly one of the
closedoropenflags will be there and one oflockedorunlocked.
Use cases
Displaying all banks and lockers, and the cardholders assigned to them
GET /api- Follow the link at
features.lockerBanks.lockerBanks.href↪, adding a search term to the query to thin out the results. - Follow the href of the locker bank you are after.
The results of that query are the locker bank detail.
Assigning a locker to a cardholder
Find the href of the locker, as above. Normally one that does not have someone already allocated.
Find the href of the cardholder using the cardholders API.
PATCH the cardholder with the locker's href in a property called
lockerin an element of an array calledlockers.add. Addfromanduntilproperties as you like.
Opening or quarantining a locker
Command Centre version 8.10.1112 lets you use the 'fields' parameter on the locker bank search to request the 'open' override URLs for lockers, which in prior versions was only available on their details pages. Version 9.10 adds the ability to quarantine lockers, which prevents anyone from allocating them to a cardholder.
GET /api- Follow the link at
features.lockerBanks.lockerBanks.href↪, adding?or&as appropriate thenfields=name,lockers.name,lockers.shortName,lockers.commands. Also add a search term to thin out the results, if you have a lot of locker banks. - Dig through the results for your locker bank, then through the
lockersarray of that locker bank for your locker, then into thecommandsblock of that locker for another block calledopenorquarantinedepending on your need. If your operator is able to override that locker, that block will contain a field calledhrefwhich gives the URL you need to POST to override the locker (open, for example).
Search locker banks
This returns locker banks matching your search criteria.
The result will contain no more than 100 or 100 objects depending on your version; you
should follow the next link, if it is present, to collect more.
If the result set is empty it means there are no locker banks in the divisions in which the operator has a privilege that allows listing them, such as 'View lockers and assignments'.
When you have loaded all the locker banks there will no next link.
Do not code this URL into your application. Take
it from the 'href' field in the features.lockerBanks.lockerBanks section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnameshortNamedescriptiondivisionlockersnoteslockers.defaultslockers.idlockers.hreflockers.namelockers.shortNamelockers.descriptionlockers.divisionlockers.noteslockers.commandslockers.assignmentsdefaultsdefaultsquerySets which fields to return. The values you can list are the same as the field names in the details page. Using it you can return everything on the search page that you would find on the details page. Separate values with commas.
If you specify any value for this parameter, the default no longer applies and you will only receive the fields you asked for.
Use the special value defaults to return the locker bank fields you would have received
had you not given the parameter at all. Then you can add a comma and more fields.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10 you will not. If you are going to send the fields parameter and need the href or ID, be explicit.
All the field names beginning with lockers. arrived in v8.10.1112. They affect the
fields that appear inside the 'lockers' block. The guideline for using defaults is: if
you want to receive less data, specify only the fields you want. If you want to receive
more, either list every field you want or simply use lockers.defaults plus your extras.
Treat the string matches as case sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success.
The installation lacks a lockers licence, or it is missing RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/locker_banks", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/locker_banks"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/locker_banks'const response = await fetch('https://127.0.0.1:8904/api/locker_banks', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/locker_banks', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/locker_banks')
data = response.json(){
"results": [
{
"name": "Lobby",
"href": "https://localhost:8904/api/locker_banks/4566",
"description": "Behind reception",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
},
{
"name": "Bank A",
"href": "https://localhost:8904/api/locker_banks/4567",
"description": "Level 4 east A",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
}
],
"next": {
"href": "https://localhost:8904/api/locker_banks?skip=2"
}
}Get details of a locker bank
This returns details for a locker bank. In the interest of forward compatibility follow the href in the locker bank summary to get here rather than building the URL yourself.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnameshortNamedescriptiondivisionnotesconnectedControllerlockerslockers.defaultslockers.connectedController...defaultsdefaultsquerySets which fields to return. The values you can list are the same as the field names you would get in the details page. Use it to cut back on the size of the response for large locker banks. Separate values with commas.
Treat the string matches as case sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10 you will not. If you are going to send the fields parameter and need the href or ID, be explicit.
Response
Success.
The installation lacks a lockers licence, or it is missing RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.
Your REST operator does not have the privilege to view that locker bank.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/locker_banks/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/locker_banks/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/locker_banks/{id}'const response = await fetch('https://127.0.0.1:8904/api/locker_banks/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/locker_banks/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/locker_banks/{id}')
data = response.json(){
"name": "Lobby",
"href": "https://localhost:8904/api/locker_banks/4566",
"description": "Behind reception",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"lockers": [
{
"name": "Lobby locker 1",
"shortName": "L1",
"description": "Wheelchair-suitable",
"href": "https://localhost:8904/api/lockers/3456",
"assignments": [
{
"href": "https://localhost:8904/api/cardholders/325/lockers/abe3456e",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://localhost:8904/api/cardholders/325"
},
"from": "2018-01-01T00:00:00Z",
"until": "2020-01-01T00:00:00Z"
},
{
"href": "https://localhost:8904/api/cardholders/10135/lockers/deb9456f",
"cardholder": {
"name": "Messervy, Miles",
"href": "https://localhost:8904/api/cardholders/10135"
},
"from": "2018-04-01T05:00:00Z",
"until": "2018-04-07T00:00:00Z"
}
],
"commands": {
"open": {
"href": "https://localhost:8904/api/lockers/3456/open"
},
"quarantine": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"quarantineUntil": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"cancelQuarantine": {
"href": "https://localhost:8904/api/lockers/3456/cancel_quarantine"
}
}
},
{
"name": "Lobby locker 2",
"shortName": "L2",
"description": "Faulty USB charging port",
"href": "https://localhost:8904/api/lockers/3457",
"assignments": [
{
"cardholder": {
"name": "R",
"href": "https://localhost:8904/api/cardholders/10136"
},
"from": "1999-11-08T00:00:00Z",
"until": "2002-11-20T00:00:00Z"
}
],
"commands": {
"open": {
"href": "https://localhost:8904/api/lockers/3457/open"
},
"quarantine": {
"href": "https://localhost:8904/api/lockers/3457/quarantine"
},
"quarantineUntil": {
"href": "https://localhost:8904/api/lockers/3457/quarantine"
},
"cancelQuarantine": {
"href": "https://localhost:8904/api/lockers/3457/cancel_quarantine"
}
}
}
]
}Get details of a locker
This returns details for a locker. Follow the href in the locker bank summary or locker bank detail to get here rather than building the URL yourself.
Prior to v8.20 this endpoint was at /api/locker_banks/{locker_bank_id}/lockers/{id} .
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnameshortNamedescriptiondivisionassignmentsnotescommandsconnectedControllerdefaultsdefaultsquerySets which fields to return. Use it to cut back on the size of the response for a locker with many assignments. Separate values with commas.
Treat the string matches as case sensitive.
Response
Success.
The installation lacks a lockers licence, or it is missing both RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.
Your REST operator does not have the privilege to view that locker bank ('Locker assignments').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/lockers/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/lockers/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/lockers/{id}'const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/lockers/{id}')
data = response.json(){
"href": "https://localhost:8904/api/lockers/3456",
"name": "Lobby locker 1",
"shortName": "L1",
"description": "Wheelchair-suitable",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "string",
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"assignments": [
{
"href": "https://localhost:8904/api/cardholders/325/lockers/abe3456e",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://localhost:8904/api/cardholders/325"
},
"from": "2018-01-01T00:00:00Z",
"until": "2020-01-01T00:00:00Z"
},
{
"href": "https://localhost:8904/api/cardholders/10135/lockers/deb9456f",
"cardholder": {
"name": "Messervy, Miles",
"href": "https://localhost:8904/api/cardholders/10135"
},
"from": "2018-04-01T05:00:00Z",
"until": "2018-04-07T00:00:00Z"
}
],
"commands": {
"open": {
"href": "https://localhost:8904/api/lockers/3456/open"
},
"quarantine": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"quarantineUntil": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"cancelQuarantine": {
"href": "https://localhost:8904/api/lockers/3456/cancel_quarantine"
}
},
"updates": {
"href": "https://localhost:8904/api/lockers/3456/updates"
}
}Open a locker
Sends an override to open a locker.
You should get this URL from a locker detail or locker bank detail rather than building it yourself.
New in 8.10.1112.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will contain feedback from the server.
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or your operator does not have 'Override - Open Locker' on the locker's division.
That is not the URL of a locker, or it is the URL of a locker your operator does not have the privilege to see.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/lockers/{id}/open", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/lockers/{id}/open"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/lockers/{id}/open'const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}/open', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}/open', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/lockers/{id}/open')
data = response.json()Quarantine a locker
Sends an override to quarantine a locker. If the override succeeds, the locker will not be allocatable until its quarantine ends.
The call will fail if the locker is allocated to a cardholder.
You should get this URL from a locker detail or locker bank detail rather than building it yourself.
New in 9.10.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. The response body will contain feedback from the server.
Success.
Check the response body for an error message. A common fault is the locker being allocated.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or your operator does not have 'Override' on the locker's division.
That is not the URL of a locker, or it is the URL of a locker your operator does not have the privilege to see.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/lockers/{id}/quarantine", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/lockers/{id}/quarantine"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/lockers/{id}/quarantine'const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}/quarantine', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}/quarantine', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/lockers/{id}/quarantine')
data = response.json()Un-quarantine a locker
Sends an override to remove the quarantine from a locker, making it available for allocation.
You should get this URL from a locker detail or locker bank detail rather than building it yourself.
New in 9.10.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server.
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or your operator does not have 'Override' on the locker's division.
That is not the URL of a locker, or it is the URL of a locker your operator does not have the privilege to see.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine'const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine')
data = response.json()Macros
These methods give you read access to basic data about the Macros in the Command Centre database, and let you run them.
Reading the section 'Understanding Macros' in the Configuration client help is an excellent way to do exactly that.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of macros, each containing the fields you ask for in the query, including (for example) the URL you need to run the macro.
You cannot use the REST API to change a macro's schedule. You must use the Command Centre or Configuration client for that.
Overrides
You can use this API to run a macro. That is the only override they accept.
Macro status flags
Being very simple creatures macros normally bear no flags of status, but an experimental
addition to version 9.30 adds the flag closed while a macro is running. If you have
subscribed to status updates you will see this flag flash past, but if you simply ask for a
macro's status on your own schedule you are very unlikely to strike it during the extremely
brief time it is active.
This is a feature meant for Gallagher clients only. A future version may change the flag's name so we suggest avoiding it for now.
Macro flag rules
- Do not rely on
closedmeaning that the macro is running.
Use cases
Listing Macros
GET /api.- Follow the link at
features.macros.macros.href↪, appending a search term (described below) to narrow the results if your installation has a lot of macros. - Process the results, following the
nextlink until there isn't one.
Running a Macro
- Find the href for the macro using the process above.
- GET it.
- If your operator is able to run the macro, the results will contain a URL at
commands.run.href. POST to that to run the macro. No body required.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search macros
This returns a summary of the macros matching your search criteria.
The result will contain no more than 100 or 1000 macros (depending on your version), or as
many as you asked for more in your request; you should follow the next link, if it is
present, to collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any macros, such as 'View Site', 'Run Macros', or 'Schedule and Run Macros'. Perhaps there are no macros in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.macros.macros section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefidnamedescriptiondivisioncommandsnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/macros", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/macros"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/macros'const response = await fetch('https://127.0.0.1:8904/api/macros', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/macros', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/macros')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/macros/8492",
"id": "8492",
"name": "Arm lobby"
}
],
"next": {
"href": "https://localhost:8904/api/macros?skip=1000"
}
}Get details of a macro
This returns the detail of one macro.
Follow the 'href' field in an macro summary to get here.
Parameters
idstringrequiredpathThe ID of the macro.
fieldsstringhrefidnamedescriptiondivisioncommandsnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent a macro, or the operator does not have a privilege on the macro's division that allows viewing it, such as 'View Site', 'Run Macros', or 'Schedule and Run Macros'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/macros/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/macros/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/macros/{id}'const response = await fetch('https://127.0.0.1:8904/api/macros/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/macros/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/macros/{id}')
data = response.json(){
"href": "https://localhost:8904/api/macros/8492",
"id": "8492",
"name": "Arm lobby",
"description": "Arms and secures all lobby zones.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"commands": {
"run": {
"href": "https://localhost:8904/api/macros/8492/run"
}
}
}Run a macro
Sends a run request to a macro.
Parameters
idstringrequiredpathThe ID of the macro.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows running macros ('Run Macro' or 'Schedule and Run Macro', unsurprisingly).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/macros/{id}/run", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/macros/{id}/run"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/macros/{id}/run'const response = await fetch('https://127.0.0.1:8904/api/macros/{id}/run', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/macros/{id}/run', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/macros/{id}/run')
data = response.json()Operator Groups
An operator group is like an access group in that:
- one may contain any number of cardholders, and
- a cardholder can be a member of any number of operator groups.
However:
there is no lineage: an operator group has no parent,
memberships have no begin or end dates,
a cardholder can have only one membership per group (and, because of the previous point, needs only one),
operator group updates do not go to controllers, so editing them is a much cheaper operation.
The biggest difference is in their purpose, of course. They grant cardholders the use of software instead of doors.
These API methods give you read access to the system's operator groups. You can search them, list their cardholder members, and see the divisions into which they grant their privileges. The API will not tell you the privileges that they grant.
Your REST operator will need either the 'View' or 'Edit Operators' operator privilege for those GETs.
Operator groups first appeared in the API in version 8.50.
Use cases
These are practically identical to the operations you'd perform on an access group, simplified because of the lack of lineage.
Finding members of an operator group, and managing memberships.
GET /api.Follow the link at
features.operatorGroups.operatorGroups.href↪ (adding search terms, and settingtophigh to save pagination). 3. Find your operator group.The link at
cardholdersreturns the cardholders who have membership of your group.Get the link at
cardholders(which is the same on the search results and the details page), look in that cardholder'soperatorGroupsarray, and manage each membership as you require. Send a DELETE to a membership's href to remove it, or an HTTP PATCH to the cardholder href to update it.
Search operator groups
This returns operator groups matching your search criteria.
The result will contain a batch of groups; you should follow the next link, if it is
present, to collect the next batch.
When you have loaded all the operator groups there will be no next link.
If your result set is empty it means either your search terms were too tight or your operator does not have the privilege to view any operator groups. Perhaps there are none in the divisions in which your operator has 'View operators' or 'Edit operators', or your operator does not have those privileges at all.
This request does not return the group's cardholders. That would make the results unwieldy. Instead, it provides a separate link.
Adding, deleting, or modifying operator groups between calls to this API will not affect the pagination of its results if you sort by ID.
You can find the URL for this call in the features.operatorGroups.operatorGroups.href
field of /api. In the interest of forward compability, do not build it
yourself.
Added in 8.50.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnamedescriptiondivisionnotescardholdersdivisionsdefaultsdefaultsqueryposintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/operator_groups", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/operator_groups"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/operator_groups'const response = await fetch('https://127.0.0.1:8904/api/operator_groups', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/operator_groups', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/operator_groups')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/operator_groups/523",
"name": "Locker admins.",
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://localhost:8904/api/operator_groups?skip=61320"
}
}Get details of an operator group
In addition to the group's vitals and a link to the membership document, this call returns the divisions in which the operator groups grants its privileges.
Note that you can obtain the same results by adding
fields=defaults,description,division,divisions,cardholders to a
search.
You can find the URL for this call in the operator group search results and in a
cardholder's operatorGroups array. In the interest of forward compability, do not build
it yourself.
Added in 8.50.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnamedescriptiondivisionnotescardholdersdivisionsdefaultsdefaultsquerySets which fields to return. The values you can list are the same as the field names in the detail results. Use it to return the notes, which don't appear by default. Separate values with commas.
Treat the string matches as case sensitive.
Response
Success.
The site does not have the RESTCardholders licence.
That is not the URL of an operator group, or it is the URL of an operator group that the operator does not have the privilege to see ('View' or 'Edit Operators').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/operator_groups/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/operator_groups/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/operator_groups/{id}'const response = await fetch('https://127.0.0.1:8904/api/operator_groups/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/operator_groups/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/operator_groups/{id}')
data = response.json(){
"href": "https://localhost:8904/api/operator_groups/523",
"name": "Locker admins.",
"serverDisplayName": "ruatoria.satellite.int",
"description": "For managing locker assignments.",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"cardholders": {
"href": "https://localhost:8904/api/operator_groups/523/cardholders"
},
"divisions": [
{
"division": {
"name": "Staff",
"href": "https://localhost:8904/api/divisions/647"
}
},
{
"division": {
"name": "Contractors",
"href": "https://localhost:8904/api/divisions/649"
}
}
]
}Get membership of an operator group
This lists all cardholders who are members of the group identified by the request URL. It
does not paginate the results, so there is no next link.
Operator groups cannot contain other operator groups, so every cardholder benefiting from this operator group comes out of this call.
If your operator does not have the privilege to view a cardholder item you will receive its name but not its href (since following the href would 404).
You can find this call's URL in the cardholders block of an operator group. In the
interest of forward compability, do not build it
yourself.
Added in 8.50.
There are two hrefs per cardholder. The lower-level href is the identifier for the
cardholder, found throughout this API. The higher-level href field only appears if you
ask for it using the fields query parameter, and only if the server is 8.70 or later. It
refers to the cardholder's membership in the operator group. If you DELETE it, you will
remove the cardholder from this operator group (which will demote them from an operator to a
regular cardholder if this was their last remaining operator group).
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>defaultscardholderhrefdefaultsquerySpecifies the fields you want in the search results. The only two candidates are
cardholder and href. The default is fields=defaults, which is the same as
fields=cardholder. Separate values with commas.
Response
Success.
You do not have privileges to see the operator group. Check the body of the result for a description of the problem.
The ID is invalid, or it is valid but you do not have privileges to see the operator group.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/operator_groups/{id}/cardholders", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/operator_groups/{id}/cardholders"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/operator_groups/{id}/cardholders'const response = await fetch('https://127.0.0.1:8904/api/operator_groups/{id}/cardholders', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/operator_groups/{id}/cardholders', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/operator_groups/{id}/cardholders')
data = response.json(){
"cardholders": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://host.com:8904/api/cardholders/325"
}
},
{
"href": "https://host.com:8904/api/cardholders/329/operator_groups/AFADEMED",
"cardholder": {
"name": "Miles Messervy"
}
}
]
}Outputs
These methods give you read access to Outputs in the Command Centre database, and let you override them.
The first use case below introduces the main entry point. It is a paginated search interface that gives you any number of outputs, each containing the fields you ask for in the query, including (for example) the URLs you need to switch the outputs on and off.
Override times
- End-times on overrides are not accurate to the second. Internally, Command Centre converts the end time to a duration, so you may find that submitting end times in the very near future does not have the exact effect you expect.
- The end time you set for an override cannot be in the past or more than 24 hours into the future.
Overrides always use 'on' and 'off'
The Configuration client allows you to assign different display strings to the two normal output states, on and off. On could be 'green', for example, and off could be 'red'. Regardless, the overrides you apply to an output are called 'on' and 'off'.
Output status flags
Status flags, on the other hand, use the language of the relays on the hardware modules. They will report 'closed' for an output that is on, and 'open' for one that is off.
If the output is online, its statusFlags field may contain one or more of these flags:
relayStateUnknownmeans the controller does not know what the output should be doing.closedmeans the output relay is closed.openmeans the output relay is open.pulsedmeans the relay's change in state is momentary.switchingDisabledmeans switching this output is disabled.overriddencan appear whether the output is online or offline. It means the output has an override in effect.
Output flag rules
- If and only if the output is online, one of 'relayStateUnknown', 'closed', or 'open' will appear. That is your test for whether an output is in error.
- Of the above, only 'overridden' can appear when the output is offline.
Use cases
Listing Outputs
GET /api.- Follow the link at
features.outputs.outputs.href↪, appending a search term such asname=substringto select the outputs, andfieldsto tell the server what to return about each. The next section covers those query parameters. - Process the results, following the
nextlink until there isn't one.
Switching an Output
- Find the href for the output using the process above.
- GET it.
- Look in the
commandsstructure of the results to find the API URLs that turn the output on, off, or cancel a previous override. Use theuntilvariants if you want to specify an end time. - POST to that URL. Those with
untilin their command block keys require a JSON object in the body; the others expect it empty.
Finding an output's status
- Find the href for the output using the process above, and GET it.
- Take the
updates↪ href from that page. - GET it.
- Use the flag rules above to interpret the status flags you receive.
- Follow the
nextlink to stay up to date.
Licensing
All the POSTs that override items require RESTOverrides.
If you have RESTOverrides but not RESTStatus in 8.60 or later, the GETs return enough information to let you find the item you want to override but they do not return its status. In 8.50 and older, the GET will fail without RESTStatus.
The RESTOverrides and RESTStatus licences do not overlap: to watch the status of an item as well as override it, you will need both.
Search outputs
This returns a summary of the outputs matching your search criteria.
The result will contain no more than 100 or 1000 outputs (depending on your version), or as
many as you asked for more in your request; you should follow the next link, if it is
present, to collect the next batch.
If your result set is empty it means your operator does not have the privilege to view any outputs, such as 'View Site', 'Edit Site', or 'Override'. Perhaps there are no outputs in the divisions in which your operator has privileges, or your operator has no privileges at all.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.outputs.outputs section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/outputs", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/outputs"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/outputs'const response = await fetch('https://127.0.0.1:8904/api/outputs', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/outputs')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/outputs/2365",
"id": "2365",
"name": "Studio door red/green"
}
],
"next": {
"href": "https://localhost:8904/api/outputs?skip=1000"
}
}Get details of an output
This returns the detail of one output.
Follow the 'href' field in an output summary to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
In v8.00 you will receive the href and internal ID even if you did not ask for them. In 8.10
and later you will only get what you asked for. If you are going to send the fields
parameter and need the href or ID, be explicit.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an output, or the operator does not have a privilege on the output's division that allows viewing outputs, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/outputs/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/outputs/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/outputs/{id}'const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/outputs/{id}')
data = response.json(){
"href": "https://localhost:8904/api/outputs/2365",
"id": "2365",
"name": "Studio door red/green",
"description": "Red or green, controlled from sound desk.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/outputs/2365/updates/0_0_0"
},
"statusFlags": [
"open",
"overridden"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"on": {
"href": "https://localhost:8904/api/outputs/2365/on"
},
"onUntil": {
"href": "https://localhost:8904/api/outputs/2365/on"
},
"off": {
"href": "https://localhost:8904/api/outputs/2365/off"
},
"offUntil": {
"href": "https://localhost:8904/api/outputs/2365/off"
},
"pulse": {
"href": "https://localhost:8904/api/outputs/2365/pulse"
}
}
}Turn on an output
Sends an override to close an output.
If you send an end time in the body, the override will only stay in effect until then.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding outputs (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/outputs/{id}/on", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/outputs/{id}/on") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/outputs/{id}/on' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/on', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/on', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/outputs/{id}/on', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Pulse an output
Sends an override to pulse an output.
Pulsing an output differs from turning it on in two ways:
You cannot specify a duration for it to stay activated, because that comes from the output's configuration.
A pulsed output will stay on for its pulse time even if another event seeks to deactivate it (an 'off' override will still deactivate the output).
Added in 8.50.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The output is not configured for pulsing (8.70 and later only).
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding outputs (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/outputs/{id}/pulse", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/outputs/{id}/pulse"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/outputs/{id}/pulse'const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/pulse', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/pulse', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/outputs/{id}/pulse')
data = response.json()Turn off an output
Sends an override to open an output.
If you send an end time in the body, the override will only stay in effect until then.
Body
Put this in the body of override POSTs to set the time at which the override should cease. The API will reject the override if the string is not empty and it cannot parse it into a date-time, but it will treat the override as having no end time if you mis-spell 'endTime' or if you send a blank string.
Command Centre computes an override's duration to a whole number of minutes with a minimum of one. That means that a timed override will always end at a multiple of sixty seconds from the time the hardware receives the override request, which means the override will end within thirty seconds of the time you supplied here. In versions older than 8.80, the discrepancy may be up to a minute.
Careful observation of overrides submitted from the Configuration client and from the API will reveal that they use different rounding methods. Be assured, both result in overrides ending within a minute of the requested time.
endTimestring<date-time>Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The server could not parse the POST parameters. There could be a syntax error in your JSON.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding outputs (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"endTime": "2018-07-31T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/outputs/{id}/off", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"endTime": "2018-07-31T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/outputs/{id}/off") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/outputs/{id}/off' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2018-07-31T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/off', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/off', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"endTime": "2018-07-31T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"endTime": "2018-07-31T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/outputs/{id}/off', json=payload)
data = response.json(){
"endTime": "2018-07-31T00:00:00Z"
}Cancel an override
Cancels an override, returning the output to its previous state.
Parameters
idstringrequiredpathAn internal identifier.
requested_bystringqueryAttributes this override to the cardholder with this ID rather than the REST operator. Privilege checks will use the operator as normal, but event monitors and reports will state that the person responsible for the override was the attributed cardholder, not the REST operator. The REST operator will appear in a special mention in the event's details.
First available in 8.70. Versions older than 8.70 will ignore the parameter, leaving the override attributed to the REST operator.
Response
Success.
The site does not have the RESTOverrides licence (in which case the body of the result will say so), or the operator does not have a privilege that allows overriding outputs (such as 'Override', or 'Maintenance Override' for shunts).
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/outputs/{id}/cancel", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/outputs/{id}/cancel"));
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/outputs/{id}/cancel'const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/cancel', {
method: 'POST',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/cancel', {
method: 'POST',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.post('https://127.0.0.1:8904/api/outputs/{id}/cancel')
data = response.json()Monitor an output
See the item status topic for how to use the updates APIs.
Note that this API call is good for watching one item only; if you want to monitor several, you are better off with a status subscription.
Follow the 'updates' field in an output summary or details pages to get here.
Parameters
idstringrequiredpathAn internal identifier.
fieldsstringstatusstatusTextstatusFlagsqueryThis instructs the server to return these fields in the update, instead of the default set. You will not hear about updates to fields you do not list.
Response
Success. See the introduction for a description of the three status fields.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent an output, or the operator does not have a privilege on the output's division that allows viewing outputs, such as 'View Site', 'Edit Site', or 'Override'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/outputs/{id}/updates", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/outputs/{id}/updates"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/outputs/{id}/updates'const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/updates', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/outputs/{id}/updates', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/outputs/{id}/updates')
data = response.json(){
"updates": {
"status": "This Output is Off.",
"statusText": "This Output is Off.",
"statusFlags": [
"open"
]
},
"next": {
"href": "https://localhost:8904/api/outputs/2365/updates/9_1"
}
}PDF definitions
A Personal Data Field is an item that adds a custom value to a cardholder. Each PDF has a type (text, image, numeric, ...) and optional constraints on the values that it can hold. For example, text, email, and telephone number types can have a regular expression with which new values must match. A date can have a maximum and a minimum. Text PDFs can have a list of valid values, like an enumeration. Mobile numbers and email addresses have a flag indicating whether they are suitable to receive SMS and email notifications.
There is more configuration: image PDFs have a type and size, to which Command Centre will transcode incoming images. All PDFs have their own access level (hidden, read-only, or full access) that applies to operators in operator groups that do not expressly override it.
Importantly, PDFs are attached to access groups. A cardholder can have a value for a PDF only if he or she is a member of one of the PDF's access groups.
This API lets you see the definitions and configuration of all the personal data fields in the system.
API routes that allow creating, modifying, and deleting PDF definitions are in development.
Use cases
Finding all a cardholder's PDF definitions
You can see all a cardholder's PDF values by looking in the personalDataDefinitions block of
a cardholder. But that will only show you the PDFs that the
cardholder currently has values for -- it will not show you the blanks. If you are writing an
application that needs to find all the PDFs that a cardholder could carry, you will need this
process.
Recall that a PDF is attached to an access group and appears on all direct and indirect members of that access group. To find a cardholder's PDFs, including those for which the cardholder has no value, you must find all the cardholder's groups, then find all the PDFs on those groups.
- GET the cardholder's access groups from the cardholder's detail page, or the search page
with
accessGroupsadded to the query'sfieldsparameter. - Iterate through the hrefs of those access groups, GETting each. That will return their detail pages.
- For each access group detail page, record the names and hrefs from the
personalDataDefinitionsblock and add theparent.hreflink to the list of groups to check (unless you have already seen it, of course).
Now you have the names and hrefs of all the PDFs that cardholder can hold.
Finding a PDF's regular expression
GET /api- Follow the link at
features.personalDataFields.personalDataFields.href↪, adding a search term to the query to thin out the results. For example, addname="PDF name"to only return the PDF definitions with that name.
Setting a cardholder's PDF value
You do that by PATCHing a cardholder with a field named after the PDF following an '@'.
Licensing
Reading PDF definitions is enabled by the RESTCardholders licence but creating, deleting, or modifying them requires the RESTConfiguration licence.
Search PDF definitions
This returns the PDF definitions you are privileged to view. You will need the 'View Personal Data Definitions' privilege, or its 'Edit' equivalent, on a PDF's division in order to see it.
The result will contain no more than 100 or 1000 objects, depending on your version; you
should follow the next link, if it is present, to collect more. When you have loaded all
the PDF definitions there will no next link.
Do not code this URL into your application. Take
it from the href in the features.personalDataFields.personalDataFields section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefidnamedescriptiondivisionserverDisplayNametypedefaultrequireduniquesortPriorityaccessGroupsregexregexDescriptiondefaultAccessoperatorAccessnotificationDefaultdefaultsdefaultsquerySpecifies the fields in the search results. The values you can list are the same in the search and details pages. Using it you can return everything on the search page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success.
The site does not have the RESTCardholders or RESTEvents licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/personal_data_fields", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/personal_data_fields"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/personal_data_fields'const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/personal_data_fields')
data = response.json(){
"results": [
{
"name": "email",
"id": "5516"
},
{
"name": "cellphone",
"id": "9998",
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://localhost:8904/api/personal_data_fields?pos=900&sort=id"
}
}Create [coming]
Creates a new personal data definition.
Do not code this URL into your application. Take
it from the href field in the features.personalDataFields.personalDataFields.href field
of GET /api.
When successful it returns a location header containing the address of the new PDF.
Note that you can only create one per POST.
This call requires the RESTConfiguration licence.
Body
This example shows the fields that you can set when creating a PDF.
/api/personal_data_fields returns an array of these. By default it gives you just the
basics about a PDF: its ID, href, name, and (if it is remote) the name of its home server.
By using the fields parameter you can add more.
idstringread onlyAn alphanumeric identifier, unique to the server. Use it to filter cardholder searches and to add your external ID to the results of an event search.
namestringserverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division containing this PDF definition. Required when creating one.
typestringstringimagestrEnumnumericdateaddressphoneemailmobileThe type of PDF: string, image, email address, etc.
Required when creating a new PDF definition, but ignored when PATCHing one since a PDF's type is fixed once set.
defaultstringThis is the value that new cardholders will receive, if not supplied by the creating client. Required for 'required' PDFs.
This field will be missing if there is no default value.
To clear the default, send the blank string "".
requiredbooleanfalseIf true, every cardholder with this PDF must have a value for it. No blanks allowed.
uniquebooleanfalseIf true, every cardholder with a value for this PDF must have a different value.
After 8.70 this will not show for date and image PDFs, because it can never be true for them.
defaultAccessstringnoAccessreadOnlyfullAccessfullAccessThis is the access that operators will have to cardholders' values of this PDF if the operator is not a member of an operator group that overrides it. Check the operator group's 'Personal data' tab in the Configuration Client.
operatorAccessstringnoAccessreadOnlyfullAccessread onlyThis is the access that your operator has to cardholders' values of this PDF including the
permissions granted by this cardholder's operator groups. You will only
get this field if you ask for it with the fields parameter.
New in 8.80.
sortPriorityintegerThis is called 'sort order' in the Configuration Client. Interactive clients use this number to order the list of PDFs on a cardholder. It has no effect on access control or this API.
accessGroupsArray<object>read onlyThis array contains a block for each access group that gives this PDF to its members. Each block contains the group's name, and if the operator has read access to the group, its href.
notificationDefaultbooleanThis value is copied to the 'notification' flag on a cardholder's value for this PDF when
they first gain membership of one of this PDF's access groups. It will only appear for
PDF types that can receive notifications (email addresses and mobile numbers), and only if
you ask for it with the fields parameter. The server will ignore it if you send it in a
POST or PATCH to a PDF that is not email or mobile.
New in 8.50.
regexstringThis is the regular expression that a string-valued PDF value must match before Command Centre will accept it on a cardholder.
To remove the requirement that PDF values match a regular expression, send the empty
string "".
regexDescriptionstringRegular expressions often need explaining.
imageWidthintegerThe maximum width of an image stored in this PDF, in pixels, for image PDF types. You
will only get this field if you ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.50.
imageHeightintegerThe maximum height of an image stored in this PDF, in pixels, for image PDF types. You
will only get this field if you ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.50.
imageFormatstringbmpjpgpngdeprecatedWhether this image PDF stores BMPs, JPEGs, or PNGs. You will only get this field if you
ask for it with the fields parameter.
New in 8.50. Deprecated in 8.70 by contentType, which is more standard.
contentTypestringimage/bmpimage/jpegimage/pngWhether this image PDF stores BMPs, JPEGs, or PNGs. You will only get this field if you
ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.70.
isProfileImagebooleanTrue if and only if this PDF holds images and it is set as a profile image.
Unlike many other properties of an image PDF, this can be changed at will.
New in 8.70.
Response
Success.
The body of the POST did not describe a valid PDF.
If you see 'Invalid Personal Data Field JSON object', the server could not parse the JSON in the body of your POST. Remember to quote all strings, especially those than contain @ symbols.
The operator does not have a privilege that allows creating PDFs or the server does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/personal_data_fields", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/personal_data_fields") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/personal_data_fields' \
-H 'Content-Type: application/json' \
-d '{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}'const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": False,
"unique": False,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": False,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": False
}
response = requests.post('https://127.0.0.1:8904/api/personal_data_fields', json=payload)
data = response.json(){
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}Get details of a PDF definition
This returns details for a PDF definition. Follow the href in the summary to get here, rather than building it yourself.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefidnamedescriptiondivisionserverDisplayNametypedefaultrequireduniquesortPriorityaccessGroupsregexregexDescriptiondefaultAccessoperatorAccessnotificationDefaultdefaultsdefaultsquerySpecifies the fields in the search results. The values you can list are the same in the search and details pages. Using it you can return everything on the search page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
Response
Success.
The site does not have the RESTCardholders or RESTEvents licence
The operator does not have a privilege that allows viewing that PDF's definition ('View' or 'Edit Personal Data Definition'), or the PDF's own access control is hiding it from the operator.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/personal_data_fields/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/personal_data_fields/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/personal_data_fields/{id}'const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/personal_data_fields/{id}')
data = response.json(){
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}Remove [coming]
This call removes a PDF item from Command Centre. Follow the href in the summary to get here, rather than building it yourself.
It requires the RESTConfiguration licence.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Success.
Deleting failed. This happens when the PDF is in use.
The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.
That is not the URL of a PDF, or the operator is not privileged to view it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/personal_data_fields/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/personal_data_fields/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/personal_data_fields/{id}'const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/personal_data_fields/{id}')
data = response.json()Update [coming]
This is the call you use to update a PDF. Follow the href in the summary to get here, rather than building it yourself.
The PATCH expects a document in the same format as the the PDF detail.
It requires the RESTConfiguration licence.
Body
There are no mandatory fields when PATCHing. There are, however, some fields you cannot change.
/api/personal_data_fields returns an array of these. By default it gives you just the
basics about a PDF: its ID, href, name, and (if it is remote) the name of its home server.
By using the fields parameter you can add more.
idstringread onlyAn alphanumeric identifier, unique to the server. Use it to filter cardholder searches and to add your external ID to the results of an event search.
namestringserverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division containing this PDF definition. Required when creating one.
typestringstringimagestrEnumnumericdateaddressphoneemailmobileThe type of PDF: string, image, email address, etc.
Required when creating a new PDF definition, but ignored when PATCHing one since a PDF's type is fixed once set.
defaultstringThis is the value that new cardholders will receive, if not supplied by the creating client. Required for 'required' PDFs.
This field will be missing if there is no default value.
To clear the default, send the blank string "".
requiredbooleanfalseIf true, every cardholder with this PDF must have a value for it. No blanks allowed.
uniquebooleanfalseIf true, every cardholder with a value for this PDF must have a different value.
After 8.70 this will not show for date and image PDFs, because it can never be true for them.
defaultAccessstringnoAccessreadOnlyfullAccessfullAccessThis is the access that operators will have to cardholders' values of this PDF if the operator is not a member of an operator group that overrides it. Check the operator group's 'Personal data' tab in the Configuration Client.
operatorAccessstringnoAccessreadOnlyfullAccessread onlyThis is the access that your operator has to cardholders' values of this PDF including the
permissions granted by this cardholder's operator groups. You will only
get this field if you ask for it with the fields parameter.
New in 8.80.
sortPriorityintegerThis is called 'sort order' in the Configuration Client. Interactive clients use this number to order the list of PDFs on a cardholder. It has no effect on access control or this API.
accessGroupsArray<object>read onlyThis array contains a block for each access group that gives this PDF to its members. Each block contains the group's name, and if the operator has read access to the group, its href.
notificationDefaultbooleanThis value is copied to the 'notification' flag on a cardholder's value for this PDF when
they first gain membership of one of this PDF's access groups. It will only appear for
PDF types that can receive notifications (email addresses and mobile numbers), and only if
you ask for it with the fields parameter. The server will ignore it if you send it in a
POST or PATCH to a PDF that is not email or mobile.
New in 8.50.
regexstringThis is the regular expression that a string-valued PDF value must match before Command Centre will accept it on a cardholder.
To remove the requirement that PDF values match a regular expression, send the empty
string "".
regexDescriptionstringRegular expressions often need explaining.
imageWidthintegerThe maximum width of an image stored in this PDF, in pixels, for image PDF types. You
will only get this field if you ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.50.
imageHeightintegerThe maximum height of an image stored in this PDF, in pixels, for image PDF types. You
will only get this field if you ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.50.
imageFormatstringbmpjpgpngdeprecatedWhether this image PDF stores BMPs, JPEGs, or PNGs. You will only get this field if you
ask for it with the fields parameter.
New in 8.50. Deprecated in 8.70 by contentType, which is more standard.
contentTypestringimage/bmpimage/jpegimage/pngWhether this image PDF stores BMPs, JPEGs, or PNGs. You will only get this field if you
ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.70.
isProfileImagebooleanTrue if and only if this PDF holds images and it is set as a profile image.
Unlike many other properties of an image PDF, this can be changed at will.
New in 8.70.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid PDF. PDFs have many rules, easily enforced in a graphical interface but less so in an API. See the body of the response for help on what went wrong.
The operator has a privilege that allows viewing the item but not modifying it, or you tried to set the division to one you cannot configure, or the server is missing the necessary licence.
You need the 'Edit Personal Data Definitions' privilege on the item you are changing, which means you need on it on its current division. You also need it on the new division, if you are changing that.
This is also the response when the server does not have the 'RESTConfiguration' licence.
That is not the URL of a PDF or your operator does not have the privilege to view it. This probably means you have built the URL yourself instead of taking it from the results of a GET.
The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/personal_data_fields/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/personal_data_fields/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/personal_data_fields/{id}' \
-H 'Content-Type: application/json' \
-d '{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}'const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/personal_data_fields/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": False,
"unique": False,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": False,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": False
}
response = requests.patch('https://127.0.0.1:8904/api/personal_data_fields/{id}', json=payload)
data = response.json(){
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}PIV cards
This tag is a supplement to the main cardholder API documentation ('cardholder' is the Command Centre term for a user). That section describes how to add a card to an existing cardholder in a PATCH, and how to create a cardholder with cards in a POST, but in the interests of brevity its examples do not cover PIV.
The schema / models section gives you the details of the PIV part of a Command Centre card, with examples of what you would submit to create or update one, and what Command Centre will send you when you view one.
The Paths section takes those schema examples and wraps them into a PATCH and a POST. The two differ only in how they wrap the card into the submission body.
If your application will be assigning PIV cards to cardholders, however, the first thing it needs to do is learn the value of some constants for your particular installation of Command Centre.
Finding the PIV card type
When you assign any card to a cardholder, PIV or otherwise, you need to provide the identifier of the card type. It will vary between Command Centre installations, so you cannot use a value from another installation or these examples. It will not change while Command Centre is running but it may change at upgrade, so your application should follow this process at startup.
It takes two queries and a loop:
GET /api.- If running 8.00 or earlier, follow the link at
features.cardTypes.cardTypes.href(which will be to/api/card_types), or - if running 8.10 or later, follow the link at
features.cardTypes.assign.href(which will probably be to/api/card_types/assign. Both URLs will work in 8.10, but the advantage of this URL is that your operator can access it at a lower privilege level). - Iterate through the array to find the element with
credentialClass: piv, and - note its
href.
You can accomplish the last two steps with the JSONPath filter
$.results[?(@.credentialClass=='piv')].href.
Explanation: Command Centre ships with a handful of card types, and administrators can add more, but the one that Command Centre uses for PIV and PIV-I cards has its own credential class. It will look like the example in the GET.
Finding the URL to create cardholders
GET /api. The link is at features.cardholders.cardholders.href.
Finding the URL of a cardholder
See the main cardholder documentation, particularly the section on searching cardholders.
Licensing
All of the API calls described here require the RESTCardholders licence. If your site is also licensed to use PIV cards, you can access those cards via REST.
Cardholder API changes in 8.10
- Certificates and biometric data are now available without a customisation. They are too large
to send to all REST clients so you must ask for them using the
fieldsparameter.
Create a cardholder with a PIV card
This shows how to create a cardholder with a PIV card.
Body
When creating a cardholder there are many fields you can send that are not shown here, but since this is the PIV section of the documentation it only shows how to give the new person a PIV card.
firstNamestringYou must set either a first or last name when creating a cardholder.
divisionobjectrequiredYou must set a division when creating a cardholder.
cardsArray<NewPIVCard>An array of cards to give to the new cardholder.
Response
Success.
The body of the POST did not describe a valid cardholder.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"firstName": "Nick",
"division": {
"href": "string"
},
"cards": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders [PIV]", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"firstName": "Nick",
"division": {
"href": "string"
},
"cards": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders [PIV]") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders [PIV]' \
-H 'Content-Type: application/json' \
-d '{
"firstName": "Nick",
"division": {
"href": "string"
},
"cards": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders [PIV]', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"firstName": "Nick",
"division": {
"href": "string"
},
"cards": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders [PIV]', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"firstName": "Nick",
"division": {
"href": "string"
},
"cards": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"firstName": "Nick",
"division": {
"href": "string"
},
"cards": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}
response = requests.post('https://127.0.0.1:8904/api/cardholders [PIV]', json=payload)
data = response.json()When sent in a [POST](#path-Cardholders/post/api/cardholders) this body would create a PIV credential and assign it to the cardholder being created by the POST. A PIV-I example would have a different style of FASC-N, and a card number to match.
{
"firstName": "Nick",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/244"
},
"number": "47000256001337111234567890199991",
"status": {
"value": "active"
},
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}Get details of a cardholder
You will receive this URL from other calls to the API, in a search result or an access group membership list, for example. The URL is the unique identifier of a cardholder, and calling it returns much of what Command Centre holds for that person, including assigned cards.
The result includes an array called cards. Each element of that array is a credential of
some type. If it is a PIV card, it will look like the example here.
Note that this example is an array of one.
Only the PIV card schema is shown below. The main documentation covers all the other fields Command Centre holds for a cardholder.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
The operator does not have a privilege that allows reading cardholders, or there is no cardholder at that address.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/cardholders/{id} [PIV]", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/cardholders/{id} [PIV]"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/cardholders/{id} [PIV]'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id} [PIV]', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id} [PIV]', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/cardholders/{id} [PIV]')
data = response.json(){
"cards": [
{
"href": "https://localhost:8904/api/cardholders/325/cards/6284082f7ba5eb1",
"number": "3165-4313-245789-098765432113456799",
"issueLevel": 1,
"status": {
"value": "Not Trusted",
"type": "inactive"
},
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"lastCheckTime": "2018-04-26T00:22:05Z",
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
]
}Update a cardholder's PIV cards
This shows how to update a cardholder's PIV cards.
Body
To update a cardholder's cards or give them a new one, send an object called cards
containing up to three arrays named 'add', 'update', and 'remove'. Every element you put
in those arrays should be in the card schema shown here.
cardsobjectParameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will add feedback from the server about your PATCH.
Success, with no feedback.
The parameters are invalid, or other errors prevented the update.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"cards": {
"add": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
],
"update": [
{
"href": "string",
"status": {
"value": "string"
},
"pivData": {
"pivStatus": {
"type": "string"
}
}
}
],
"remove": [
{
"href": "string",
"status": {
"value": "string"
}
}
]
}
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/cardholders/{id} [PIV]", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"cards": {
"add": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
],
"update": [
{
"href": "string",
"status": {
"value": "string"
},
"pivData": {
"pivStatus": {
"type": "string"
}
}
}
],
"remove": [
{
"href": "string",
"status": {
"value": "string"
}
}
]
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/cardholders/{id} [PIV]") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/cardholders/{id} [PIV]' \
-H 'Content-Type: application/json' \
-d '{
"cards": {
"add": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
],
"update": [
{
"href": "string",
"status": {
"value": "string"
},
"pivData": {
"pivStatus": {
"type": "string"
}
}
}
],
"remove": [
{
"href": "string",
"status": {
"value": "string"
}
}
]
}
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id} [PIV]', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"cards": {
"add": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
],
"update": [
{
"href": "string",
"status": {
"value": "string"
},
"pivData": {
"pivStatus": {
"type": "string"
}
}
}
],
"remove": [
{
"href": "string",
"status": {
"value": "string"
}
}
]
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/{id} [PIV]', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"cards": {
"add": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
],
"update": [
{
"href": "string",
"status": {
"value": "string"
},
"pivData": {
"pivStatus": {
"type": "string"
}
}
}
],
"remove": [
{
"href": "string",
"status": {
"value": "string"
}
}
]
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"cards": {
"add": [
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}
],
"update": [
{
"href": "string",
"status": {
"value": "string"
},
"pivData": {
"pivStatus": {
"type": "string"
}
}
}
],
"remove": [
{
"href": "string",
"status": {
"value": "string"
}
}
]
}
}
response = requests.patch('https://127.0.0.1:8904/api/cardholders/{id} [PIV]', json=payload)
data = response.json()This example would update an existing PIV card on an existing cardholder. The two fields it changes are the status, which is a field common to credentials of all types, and the PIV status. The PIV status is the only PIV-specific data you can change on a PIV card. Everything else--the CHUID, certificates, and biometrics--are all fixed, once set. See the [PIV data schema](#definition-NewPIVCard) for when you should set the PIV status and what you can set it to.
{
"cards": {
"update": [
{
"href": "https://host.com:8904/api/cardholders/5398/cards/90e5d0d70",
"status": {
"value": "disabled (manually)"
},
"pivData": {
"pivStatus": {
"type": "notChecked"
}
}
}
]
}
}Receptions
A reception is an item that represents a location at which site visitors identify themselves, meet their hosts, and fulfil induction requirements. Every visit item has a reception. This controller gives you the list of receptions you can pick from when creating a visit.
This controller is read-only. It lets you pick a reception by name so that you can use its href on the visits controller.
Receptions are new to 8.50.
Use case: finding a reception by name
GET /api- Follow the link at
features.receptions.receptions.href↪ after adding search terms such asname="Front lobby". A site typically has very few receptions, so if you addtop=1000you're very unlikely to need to follow anextlink. - Find the reception you're after and use its href in a visit.
Search receptions
This returns the receptions you are privileged to view.
The result will contain no more than 100 or 1000 objects depending on your version; you
should follow the next link, if it is present, to collect more, or use the top parameter
to get more per page. Receptions are small, so setting a limit of 1000 is sensible.
When you have loaded all the receptions there will no next link.
Do not code this URL into your application. Take
it from the href in the features.receptions.receptions section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefnamedescriptiondivisionserverDisplayNamedefaultVisitorTypenotesdefaultsdefaultsquerySpecifies the fields in the response. The values you can list are the same in the search and details pages. Using it you can return everything on the search page that you would find on the details page, plus the reception's notes. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/receptions", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/receptions"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/receptions'const response = await fetch('https://127.0.0.1:8904/api/receptions', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/receptions', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/receptions')
data = response.json(){
"results": [
{
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937"
},
{
"name": "Green Dragon main desk",
"href": "https://localhost:8904/api/receptions/979"
}
],
"next": {
"href": "https://localhost:8904/api/receptions?pos=1000"
}
}Get details of a reception
This returns details for a reception. Follow the href in the summary to get here, rather than building it yourself.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefnamedescriptiondivisionserverDisplayNamedefaultVisitorTypenotesdefaultsdefaultsquerySpecifies the fields in the response. The values you can list are the same in the search and details pages. Using it you can return everything on the search page that you would find on the details page, plus the reception's notes. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
Response
Success.
The site does not have the RESTCardholders or RESTEvents licence
The operator does not have a privilege that allows viewing that reception's definition ('View Site', 'Edit Site', 'View Visits', 'Edit Visits', or 'Manage Receptions').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/receptions/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/receptions/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/receptions/{id}'const response = await fetch('https://127.0.0.1:8904/api/receptions/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/receptions/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/receptions/{id}')
data = response.json(){
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Security foyer in B1",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"defaultVisitorType": {
"href": "https://localhost:8904/api/divisions/2/v_t/925",
"accessGroup": {
"name": "Visitor group 1",
"href": "https://localhost:8904/api/access_groups/925"
}
},
"notes": "string"
}Redactions
A redaction is a process that removes one cardholder's personal information from Command Centre. There are two kinds of redaction:
An event redaction disconnects a cardholder from events that record an operation that affected that cardholder. Examples:
an operator created the cardholder,
an operator changed the value of one of the cardholder's PDFs,
an operator changed the cardholder's access group memberships, or
the cardholder moved through a door.
An event recation does not affect a cardholder's history (visible in the enterprise clients).
A cardholder information redaction is absolute. It clears all the data held against the item. After being redacted a cardholder has nothing that can identify it: no names and no PDFs.
The only way back from a redaction is a database restore.
Because redactions can take some time, and should not be allowed to run immediately for security reasons, API clients can only schedule a redaction to occur at a future date. After doing that you can check the progress of redactions, and cancel them.
Exactly how far in the future a redaction needs to be depends on the site configuration.
There are two ways to list redactions: via their own paginated interface, which returns all the redactions in the system that your operator has the permission to view, or in the cardholder record.
Performance
In early tests with SQL Server and Command Centre on a Core i5, event redactions took roughly one second per 1000 affected events. Logs and events generated when a redaction completes contain statistics you could use to calculate your own redaction rate, but expect them to vary with the other demands on the database at the time.
Search redactions
This returns all cardholders' redactions. Paginated.
Parameters
cardholderstringqueryLimits the results to redactions to cardholders with these IDs. Separate with commas.
statusstringpendinginProgresscancelleddonefailedqueryLimits the results to redactions with this status. Separate with commas.
typestringnormalEventscardholderqueryLimits the results to redactions of this type. Separate with commas.
afterstring<date-time>queryLimits the results to redactions that were scheduled to occur after this time.
beforestring<date-time>queryLimits the results to redactions that were scheduled to occur before this time.
fieldsArray<string>before cardholder finishTime href redactionOperator status type whenquerySpecifies the fields you want in the results.
Treat the string matches as case sensitive: use lastName rather than lastname.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
Response
An array of redactions, and a link to the next page if any.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/cardholders/redactions", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/cardholders/redactions"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/cardholders/redactions'const response = await fetch('https://127.0.0.1:8904/api/cardholders/redactions', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/redactions', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/cardholders/redactions')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/cardholders/redactions/625",
"type": "normalEvents",
"when": "2023-01-01T00:00:00Z",
"before": "2022-01-01T00:00:00Z",
"status": "pending",
"redactionOperator": {
"name": "REST Operator",
"href": "https://localhost:8904/api/items/100"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"finished": "2022-01-01T00:00:00Z",
"message": "Invalid cardholder",
"details": ""
}
],
"next": {
"href": "https://localhost:8904/cardholders/redactions?top=1&pos=625"
}
}Schedule a redaction
Schedules a cardholder redaction.
Body
POST one of these to schedule a redaction.
POST one of these to schedule a redaction.
These fields are common to redaction POSTs and GETs.
cardholderobjectThe href of the cardholder whose events or item this redaction should affect.
Required.
typestringnormalEventscardholderWhether this redaction is for events or cardholder information.
Required.
beforestring<date-time>For event redactions, do not redact any events after this time. No effect on cardholder information redactions.
Optional.
whenstring<date-time>When redaction is meant to happen. This should be in the future. If it is in the past, the service returns a 400.
Optional. If it is absent, it means to do it asap.
Response
Success.
The cardholder does not exist.
The operator does not have permissions to redact or to delete a cardholder, or redactions are not enabled on the server, or the server is not licensed for cardholder operations.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/cardholders/redactions", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/cardholders/redactions") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/cardholders/redactions' \
-H 'Content-Type: application/json' \
-d '{
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}'const response = await fetch('https://127.0.0.1:8904/api/cardholders/redactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/redactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}
response = requests.post('https://127.0.0.1:8904/api/cardholders/redactions', json=payload)
data = response.json(){
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}Cancel a redaction
This deletes a redaction record. It must be pending. Take the URL from the results of a redaction search.
Note that the only way to modify a redaction is to delete it and create a new one for the same cardholder. This is due to concerns about a redaction's effect on auditing.
Response
Success.
The operator does not have permissions to redact or to delete a cardholder, or the server is not licensed for cardholder operations.
No such redaction, or your operator cannot see it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/cardholders/redactions", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/cardholders/redactions"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/cardholders/redactions'const response = await fetch('https://127.0.0.1:8904/api/cardholders/redactions', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/cardholders/redactions', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/cardholders/redactions')
data = response.json()Roles
The roles API gives you basic information about Command Centre's role items so that you can use them to form relationships between cardholders.
A role defines a relationship between two cardholders. One cardholder can perform a role for many others but can have it performed for them by only one other. For example, a person can be a supervisor for many people but has only one of his or her own.
When you use REST to look up or update a cardholder, you will work on the 'has a' relationships, not the 'is a' relationships. In other words, when a role represents a connection between a person and their supervisor, the API lets you change a cardholder's supervisor but not who the cardholder supervises.
API routes that allow creating, modifying, and deleting roles are in development.
Use case: searching for a role by name and assigning a relationship
GET /api- Follow the link at
features.roles.roles.href↪ after adding search terms such asname="supervisor". - Find the href of the role with which you want to link your two cardholders.
- Find the href of the cardholder who will perform this role (the supervisor).
- Find the href of the cardholder for whom he or she will perform this role (the supervised).
- PATCH the second cardholder (the supervised) with the href of the first cardholder (the supervisor) and the href of the role.
Licensing
Reading role items is enabled by the RESTCardholders licence but creating, deleting, or modifying them requires the RESTConfiguration licence.
Search
This returns the roles you are privileged to view.
The result will contain no more than 100 or 1000 objects depending on your version; you
should follow the next link, if it is present, to collect more, or use the top parameter
to get more per page. Roles are small, so a limit of 1000 is sensible.
When you have loaded all the roles there will no next link.
Do not code this URL into your application. Take
it from the href in the features.roles.roles section of /api.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success.
The site does not have the RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/roles", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/roles"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/roles'const response = await fetch('https://127.0.0.1:8904/api/roles', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/roles', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/roles')
data = response.json(){
"results": [
{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1383"
},
{
"name": "Contract manager",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://localhost:8904/api/roles?pos=1000&sort=id"
}
}Create [coming]
Creates a new role.
Do not code this URL into your application. Take
it from the href field in the features.roles.roles.href field of GET /api.
When successful it returns a location header containing the address of the new role.
Note that you can only create one per POST.
This call requires the RESTConfiguration licence.
Body
This example shows the fields that you can set when creating a role. There are no mandatory fields when PATCHing a role, but you must specify a division when creating one.
/api/roles returns an array of these. Each element gives you enough about a role to
identify it and use it in a cardholder PATCH: its href, name, description, and (if you ask
for them using the fields parameter) notes.
In 9.10 or later you can send one of these in a POST to create a new role, or in a PATCH to modify an existing one.
namestringhrefstring<uri-reference>read onlyThis is the string to use when creating a relationship between cardholders using this role.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division containing this role. Required in a POST, optional in a PATCH.
enableCompetencyExpiryWarningsbooleanControls whether CC sends notifications to a person holding this role when their staff's competencies are about to expire.
enableCardExpiryWarningsbooleanControls whether CC sends notifications to a person holding this role when their staff's cards are about to expire.
Response
Success.
The body of the POST did not describe a valid role.
If you see 'Invalid Role JSON object', the server could not parse the JSON in the body of your POST. Remember to quote all strings, especially those than contain @ symbols.
The operator does not have a privilege that allows creating roles, or the server does not have the 'RESTConfiguration' licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/roles", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/roles") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/roles' \
-H 'Content-Type: application/json' \
-d '{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}'const response = await fetch('https://127.0.0.1:8904/api/roles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/roles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": False,
"enableCardExpiryWarnings": True
}
response = requests.post('https://127.0.0.1:8904/api/roles', json=payload)
data = response.json(){
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}Remove [coming]
This call removes a role item from Command Centre. Take its URL from a cardholder or a role search rather than building it yourself.
It requires the RESTConfiguration licence.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success.
Success.
Deleting failed. This happens when the role is in use. The usual reason is that cardholders are still connected by the role you are attempting to remove.
The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.
That is not the URL of a role, or the operator is not privileged to view it.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/roles/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/roles/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/roles/{id}'const response = await fetch('https://127.0.0.1:8904/api/roles/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/roles/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/roles/{id}')
data = response.json()Update [coming]
This is the call you use to update a role. Take its URL from a cardholder or a role search rather than building it yourself.
The PATCH expects a document in the same format as the the role detail.
This call requires the RESTConfiguration licence.
Body
There are no mandatory fields when PATCHing a role, but you must specify a division when creating one.
/api/roles returns an array of these. Each element gives you enough about a role to
identify it and use it in a cardholder PATCH: its href, name, description, and (if you ask
for them using the fields parameter) notes.
In 9.10 or later you can send one of these in a POST to create a new role, or in a PATCH to modify an existing one.
namestringhrefstring<uri-reference>read onlyThis is the string to use when creating a relationship between cardholders using this role.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division containing this role. Required in a POST, optional in a PATCH.
enableCompetencyExpiryWarningsbooleanControls whether CC sends notifications to a person holding this role when their staff's competencies are about to expire.
enableCardExpiryWarningsbooleanControls whether CC sends notifications to a person holding this role when their staff's cards are about to expire.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid card type. Card types have many rules, easily enforced in a graphical interface but less so in an API. See the body of the response for help on what went wrong.
The operator has a privilege that allows viewing the item but not modifying it, or you tried to set the division to one you cannot configure, or the server is missing the necessary licence.
You need the 'Configure Site' privilege on the item you are changing, which means you need on it on the item's current division. You also need it on the new division, if you are changing that.
This is also the response when the server does not have the 'RESTConfiguration' licence.
That is not the URL of a card type or your operator does not have the privilege to view it. This probably means you have built the URL yourself instead of taking it from the results of a GET.
The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/roles/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/roles/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/roles/{id}' \
-H 'Content-Type: application/json' \
-d '{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}'const response = await fetch('https://127.0.0.1:8904/api/roles/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/roles/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": False,
"enableCardExpiryWarnings": True
}
response = requests.patch('https://127.0.0.1:8904/api/roles/{id}', json=payload)
data = response.json(){
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}Schedules
These methods give you read/write access to seven types of schedules. Avigilon Engage schedules are not included.
The main entry point is a paginated search that gives you any number of schedules. The most useful field on a schedule is a list of state changes and the days and times that those changes should occur.
There is also a method that allows creating new schedules.
Schedules are new to 8.50.
Licensing
The schedules APIs are licensed a little differently from most others. RESTCardholders gives you Cardholder Access Schedules, while RESTStatus, RESTOverrides, and RESTConfiguration give you all schedule types.
Search schedules
This returns a summary of the schedules matching your search criteria.
The result will contain no more than 100 or 1000, depending on your version, or as many as
you asked for more in your request; you should follow the next link, if it is present, to
collect the next batch.
If your result set is empty it means your operator does not have the privilege to view schedules, such as 'View Schedules', 'Edit Schedules', or 'Schedule Access Zone'. Perhaps there are no schedules in the divisions in which your operator has privileges, or your operator has no privileges at all.
Note that the privilege 'Schedule Access Zone' only lets you see Access Zone schedules, not the other five types.
When you have loaded them all there will be no next link.
Do not code this URL into your application. Take it from the 'href' field in the
features.schedules.schedules section of /api.
Added in 8.50.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefnamedescriptiondivisionnotestypedayCategoriesdefaultsqueryThis instructs the server to return only these fields in the search results. The values you can list are the same as the field names in the details page. Using this you can return everything on the summary page that you would find on the details page. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not given
the parameter at all. Obviously only do that if you have more to add.
Treat the string matches as case-sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success. See the note in the description about privileges if your result set is empty.
The site does not have the RESTStatus, RESTOverrides, or RESTCardholders licence.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/schedules", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/schedules"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/schedules'const response = await fetch('https://127.0.0.1:8904/api/schedules', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/schedules', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/schedules')
data = response.json(){
"results": [
{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure"
}
],
"next": {
"href": "https://localhost:8904/api/schedules?skip=1000"
}
}Create a schedule
This is how you create a new schedule.
Your POST needs a body containing JSON in the same from received from a GET, containing a
division and type, and (if you want it to be useful) dayCategories.
Do not code this URL into your application. Take it from the 'href' field in the
features.schedules.schedules section of /api.
Added in 8.50.
Body
The only fields you must supply in a POST are division and type. The server can
make up a name for you, and is happy to leave the timetable empty.
/api/schedules/{id} returns one of these, and you put one of these in the body of a POST or PATCH to create or edit a schedule.
A schedule search returns an array of these. It is a
subset of what you get from a detail page at /api/schedules/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to a schedule detail.
namestringdescriptionstringdivisionobjectThe division containing this schedule. It is possible to change a schedule's division after creating it.
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
typeobjectThis block contains a string field type which will (in the server's response to a
GET) or must (in the body of your POST) be one of the seven schedule types. It is not a
valid field in a PATCH, because you cannot change the type of an existing schedule.
Elevator kiosk control schedules arrived in 8.60.
dayCategoriesArray<object>This is the part of the schedule that controls when changes occur on its scheduled items.
It is an array of objects, each containing a day category and an array of times. The
day category picks days of the year, and the times array sets what will happen and
at what times on those days.
In the body of your POST:
The
timefield of each object insidetimesmust be a string of the formHH:MMbetween 00:00 and 23:59. If you find another format that works (four zeroes, for example), it may not work in future versions.Each day category must have an entry at 00:00. This means an item does not need to search back in time to find what state it should be in when it first comes online.
Each day category cannot have more than one entry at the same time.
If you receive a 400 response to your POST or PATCH and one of those rules is the cause, the body of the response should contain a reminder.
The state field is an array describing what should happen at that time. It is
comparable to the statusFlags arrays on access zones, alarm zones, outputs, and
fence zones. For most schedule types it will contain only one word, but the extra
flag usePin may accompany access zone state changes.
When building your JSON, treat these string comparisons as case-sensitive.
All schedule types can have a state change called 'cancelUntimedOverrides'. That sets an item back to its scheduled state if it was under the effect of an override with no end time.
Other than that, each schedule type has its own set of state changes:
An Access Schedule, also known as a Cardholder Access Schedule, controls the ability of the members of an access group to pass into an access zone. The valid states are
grantanddeny. Note that 'deny' is a misnomer: a cardholder will gain access through a door if they are a member of a different access group that still has access.An Access Zone Schedule controls the mode of an access zone. The valid states are the same as the zone's status flags:
secure,dualAuth,codeOrCard, orfree. The extra flagusePinmeans the same here as it does in the status flags: people will need their PINs at readers and alarms terminals.An Alarm Zone Schedule switches an alarm zone between its four modes:
set,unset,user, anduser2. Use the wordsuser1anduser2here even if you have renamed them in the server properties.An Output Schedule can be
onoroff, plain and simple.A Notification Schedule controls when notifications go out. They can make sure that notifications go to the people who are on shift, and they can prevent bothering people at night. Like the access and output schedules it is binary, but the valid states are called
notificationEnabledandnotificationDisabled.A HV/LF Schedule controls the voltage on an electric fence. 'lowFeel' mode allows detection without the deterrant of
highVoltate.
This example sets an access zone to secure mode between 7.30am and 6:00pm on work days, and secure plus PIN mode at all other times.
scheduledItemsArray<object>read onlyAn array containing the names and hrefs of all the items that this schedule controls and that the operator has the privilege to view.
This is generated data. A schedule resides in the configuration of the items that use it, rather than the other way around, so this block is a handy aggregation of the inverse of those relationships. As such it is read-only: the server will ignore it if you send it in a POST or a PATCH.
Response
Success.
The parameters are invalid. Check the body of the response: it may be helpful.
If you see 'No schedule found', the server could not parse the JSON in the body of your
POST. Remember that the state field is an array.
The operator does not have a privilege that allows creating schedules, or the site does not have any of the RESTCardholders, RESTOverrides, RESTStatus, or (after 9.00) RESTConfiguration licences.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/schedules", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/schedules") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/schedules' \
-H 'Content-Type: application/json' \
-d '{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}'const response = await fetch('https://127.0.0.1:8904/api/schedules', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/schedules', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}
response = requests.post('https://127.0.0.1:8904/api/schedules', json=payload)
data = response.json(){
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}Get details of a schedule
This returns the detail of one schedule.
Follow the 'href' field in an schedule summary to get here.
Parameters
idstringrequiredpathThe ID of the schedule.
fieldsstringhrefnamedescriptiondivisionnotestypedayCategoriesqueryThis instructs the server to return only these fields in the details page instead of the default set. The values you can list are the same as the field names you would see in the results. Use it to cut back on the size of the response. Separate values with commas.
Treat the string matches as case-sensitive.
Response
Success.
A server running 8.50 or earlier is missing the RESTStatus licence. A server running 8.60 or later is missing both the RESTStatus and RESTOverrides licences. One of those is enough for this API call in 8.60 and later.
The request's URL does not represent a schedule, or the operator does not have a privilege on the schedule's division that allows viewing schedules, such as 'View Schedules', 'Edit Schedules', or 'Schedule Access Zone'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/schedules/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/schedules/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/schedules/{id}'const response = await fetch('https://127.0.0.1:8904/api/schedules/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/schedules/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/schedules/{id}')
data = response.json(){
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}Delete a schedule
Deletes the schedule identified by the request's URL.
Added in 8.50.
Parameters
idstringrequiredpathThe ID of the schedule. Do not add it yourself: you will receive this URL from other API calls.
Response
Success.
You cannot delete a schedule that is in use. The body of the 400 response will tell you
if that is the case. One way to find out which items are holding you up is to GET the
same URL and look in the scheduledItems block.
The operator does not have a privilege that allows deleting that schedule ('Edit Schedules'), or the site does not have any of the RESTCardholders, RESTOverrides, RESTStatus, or (after 9.00) RESTConfiguration licences.
The request's URL does not represent a schedule, or the operator does not have a privilege on the schedule's division that allows viewing schedules, such as 'View Schedules', 'Edit Schedules', or 'Schedule Access Zone'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://127.0.0.1:8904/api/schedules/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, "https://127.0.0.1:8904/api/schedules/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X DELETE 'https://127.0.0.1:8904/api/schedules/{id}'const response = await fetch('https://127.0.0.1:8904/api/schedules/{id}', {
method: 'DELETE',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/schedules/{id}', {
method: 'DELETE',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.delete('https://127.0.0.1:8904/api/schedules/{id}')
data = response.json()Modify a schedule
Modifies a schedule according to the body of the PATCH.
Added in 8.50.
Body
You do not need to supply any fields in the body of this PATCH, but if you want it to
achieve something you should add at least one. Probably dayCategories.
/api/schedules/{id} returns one of these, and you put one of these in the body of a POST or PATCH to create or edit a schedule.
A schedule search returns an array of these. It is a
subset of what you get from a detail page at /api/schedules/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to a schedule detail.
namestringdescriptionstringdivisionobjectThe division containing this schedule. It is possible to change a schedule's division after creating it.
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
typeobjectThis block contains a string field type which will (in the server's response to a
GET) or must (in the body of your POST) be one of the seven schedule types. It is not a
valid field in a PATCH, because you cannot change the type of an existing schedule.
Elevator kiosk control schedules arrived in 8.60.
dayCategoriesArray<object>This is the part of the schedule that controls when changes occur on its scheduled items.
It is an array of objects, each containing a day category and an array of times. The
day category picks days of the year, and the times array sets what will happen and
at what times on those days.
In the body of your POST:
The
timefield of each object insidetimesmust be a string of the formHH:MMbetween 00:00 and 23:59. If you find another format that works (four zeroes, for example), it may not work in future versions.Each day category must have an entry at 00:00. This means an item does not need to search back in time to find what state it should be in when it first comes online.
Each day category cannot have more than one entry at the same time.
If you receive a 400 response to your POST or PATCH and one of those rules is the cause, the body of the response should contain a reminder.
The state field is an array describing what should happen at that time. It is
comparable to the statusFlags arrays on access zones, alarm zones, outputs, and
fence zones. For most schedule types it will contain only one word, but the extra
flag usePin may accompany access zone state changes.
When building your JSON, treat these string comparisons as case-sensitive.
All schedule types can have a state change called 'cancelUntimedOverrides'. That sets an item back to its scheduled state if it was under the effect of an override with no end time.
Other than that, each schedule type has its own set of state changes:
An Access Schedule, also known as a Cardholder Access Schedule, controls the ability of the members of an access group to pass into an access zone. The valid states are
grantanddeny. Note that 'deny' is a misnomer: a cardholder will gain access through a door if they are a member of a different access group that still has access.An Access Zone Schedule controls the mode of an access zone. The valid states are the same as the zone's status flags:
secure,dualAuth,codeOrCard, orfree. The extra flagusePinmeans the same here as it does in the status flags: people will need their PINs at readers and alarms terminals.An Alarm Zone Schedule switches an alarm zone between its four modes:
set,unset,user, anduser2. Use the wordsuser1anduser2here even if you have renamed them in the server properties.An Output Schedule can be
onoroff, plain and simple.A Notification Schedule controls when notifications go out. They can make sure that notifications go to the people who are on shift, and they can prevent bothering people at night. Like the access and output schedules it is binary, but the valid states are called
notificationEnabledandnotificationDisabled.A HV/LF Schedule controls the voltage on an electric fence. 'lowFeel' mode allows detection without the deterrant of
highVoltate.
This example sets an access zone to secure mode between 7.30am and 6:00pm on work days, and secure plus PIN mode at all other times.
scheduledItemsArray<object>read onlyAn array containing the names and hrefs of all the items that this schedule controls and that the operator has the privilege to view.
This is generated data. A schedule resides in the configuration of the items that use it, rather than the other way around, so this block is a handy aggregation of the inverse of those relationships. As such it is read-only: the server will ignore it if you send it in a POST or a PATCH.
Parameters
idstringrequiredpathThe ID of the schedule. Do not add it yourself: you will receive this URL from other API calls.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The parameters are invalid. Check the body of the response: it may be helpful.
If you see 'No schedule found', the server could not parse the JSON in the body of your
request. Remember that the state field is an array.
The operator does not have a privilege that allows modifying that schedule, or the site does not have any of the RESTCardholders, RESTOverrides, RESTStatus, or (after 9.00) RESTConfiguration licences.
The request's URL does not represent a schedule, or the operator does not have a privilege on the schedule's division that allows viewing schedules, such as 'View Schedules', 'Edit Schedules', or 'Schedule Access Zone'.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/schedules/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/schedules/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/schedules/{id}' \
-H 'Content-Type: application/json' \
-d '{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}'const response = await fetch('https://127.0.0.1:8904/api/schedules/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/schedules/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}
response = requests.patch('https://127.0.0.1:8904/api/schedules/{id}', json=payload)
data = response.json(){
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}Visits
Visits are new to 8.50 and are deprecated in 9.60 onward.
Use case: finding a visit by name
GET /api- Follow the link at
features.visits.visits.href↪ after adding search terms such asname=Ginger Greeter. A site typically has many visits, so addtop=1000for efficiency's sake. - Find the visit you're after. You can then modify it by PATCHing its href.
Use case: creating a visit
- Find the href of the reception at which your visitors will arrive.
- Look at the visitor management configuration for your reception's division.
- Pick a visitor type from that configuration, and a host from that visitor type's host access groups, and (optionally) some visitor access groups from that visitor type's visitor access groups.
- Pick (or create) at least one cardholder for your visitor or visitors.
- Build a JSON payload containing all that.
- POST to the link at
features.visits.visits.href↪ (in the payload of theGET /apiyou did for the first step).
Search visits
This returns the visits you are privileged to view.
The result will contain no more than 100 or 1000 objects depending on your version; you
should follow the next link, if it is present, to collect more, or use the top parameter
to get more per page.
When you have loaded all the visits there will no next link.
Do not code this URL into your application. Take
it from the href in the features.visits.visits section of /api.
Visits are new to 8.50.
Parameters
sortstringidname-id-namequeryChanges the sort field between database ID and name.
If you prefix id or name with a minus sign (ASCII 45), the sort order is reversed.
There are two very strong reasons to sort by ID:
- Sorting by name carries a risk of missing or duplicating objects if your result set spans multiple pages and another operator is editing the database while your REST client is enumerating them. This is known as "page drift." Sorting by ID does not carry that risk.
- Following a
nextlink is dramatically quicker when sorting by ID.
We strongly recommend sorting by ID. In case you were still in doubt, we will do that by default in a future version of Command Centre.
The server silently ignores anything except the options listed here.
topinteger>= 1queryLimits the results to no more than this many items per page.
Older versions of Command Centre returned 100 items per page in the absence of this parameter. That is acceptable for GUI applications that will only display the first page, but for integrations that intend to proceed through the entire database it causes a lot of chatter.
8.70 and later versions will default to 1000 items per request. This is about where a graph of throughput versus page size begins to level out.
namestringqueryLimits the returned items to those with a name that matches this string. Without surrounding
quotes or a percent sign or underscore, it is a substring match; surround the parameter with
double quotes "..." for an exact match. Without quotes, a percent sign % will match any
substring and an underscore will match any single character.
The search is always case-insensitive. Results are undefined if you do a substring search for
the empty string (name=). You will receive no items if you search for those with no name
(name=""), as all items must have a name.
Search parameters are ANDed together.
divisionArray<string>queryLimits the returned items to those that are in these divisions.
That includes all the items in those divisions' child divisions, because Command Centre treats items as though they are also in their division's parent, and its parent, and so on up to the root division.
Separate the IDs of the divisions you are interested with commas. Item IDs are short alphanumeric strings, not URLs.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
directDivisionArray<string>queryRestricts items to those whose division is in this list.
Unlike division=, it does not follow ancestry. That means it will limit the search to items
that are directly assigned to the divisions you list, whereas division (without the
direct) will also include items that are assigned to their descendants.
List the IDs of the divisions you are interested in separated by commas. Item IDs are short alphanumeric strings, not URLs.
Because this is the first query parameter we added that contains a capital letter, this will
be the first time you have had to think about case sensitivity. directDivision works,
directdivision does not.
Results are undefined if you provide an ID that is not in the form of a division ID.
Search parameters are ANDed together.
Added in 9.20.
descriptionstringqueryLimits the returned items to those with a description that matches this string. By default it
is a substring match; surround it with double quotes "..." for an exact match. A _ will
match any single character, and a % will match any substring. With or without quotes,
having either of these wildcards in the string will anchor it at both ends as though you had
surrounded it with ".
The search is always case-insensitive. Results are undefined if you search for the empty
string (description= or description="").
Search parameters are ANDed together.
fieldsArray<string>hrefnamedescriptiondivisionserverDisplayNamenotesreceptionvisitorTypehostfromuntillocationbadgeTextvisitorAccessGroupsvisitorsvisitors.cardholdervisitors.hrefvisitors.invitationvisitors.status...defaultsdefaultsquerySpecifies the fields in the response. The values you can list are the same in the search and details pages. Using it you can return everything on the search page that you would find on the details page, plus the visit's notes. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
posintegerqueryReserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.
skipintegerqueryReserved for internal use. Do not add it to your queries.
Response
Success.
The site does not have both the RESTCardholders and VisitorManagement licences.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/visits", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/visits"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/visits'const response = await fetch('https://127.0.0.1:8904/api/visits', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/visits')
data = response.json(){
"results": [
{
"name": "Visit hosted by Greta Ginger",
"href": "https://localhost:8904/api/visits/941",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Initial scoping",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"reception": {
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/divisions/2/v_t/925",
"accessGroup": {
"name": "Visitor group 1",
"href": "https://localhost:8904/api/access_groups/925"
}
},
"host": {
"name": "Ginger Greeter",
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2021-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/visits/941/visitors/940",
"cardholder": {
"name": "Red Adair",
"href": "https://localhost:8904/api/cardholders/940"
},
"status": {
"value": "Expected Back",
"type": "expectedBack"
},
"invitation": "text to be encoded into a QR code for 940"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9040",
"cardholder": {
"name": "James Page",
"href": "https://localhost:8904/api/cardholders/9040"
},
"status": {
"value": "On-Site",
"type": "onSite"
},
"invitation": "text to be encoded into a QR code for 9040"
}
]
}
],
"next": {
"href": "https://localhost:8904/api/visits?pos=1000&sort=id"
}
}Create a visit
This creates a new visit.
In the interest of forward compatibility, do not
code its URL into your application. Take it from the href in the features.visits.visits
section of /api.
This POST expects a JSON body in the same format as a visit detail.
These fields are required:
- name
- reception
- visitor type (access group)
- host cardholder
- start time (
from) - end time (
until)
Do not code this URL into your application. Take
it from the href in the features.visits.visits section of /api.
Like all the visit endpoints, this one is new to 8.50.
Body
Pay particular attention to the field descriptions in the visit schema because some of the fields that the server sends you in response to a GET will not make sense in a POST, and some are optional.
This is a sample POST body that creates a visit when sent to /api/visits.
You need to put fewer fields in a POST than you receive from the server after a GET. Only the
reception, visitor type, host, name, and start and end date-times are required. It will
ignore href, and (unusually) division because it copies a visit's division from its
reception.
namestringrequiredYou can name a visit however you like. Gallagher's in-house applications name them after their hosts.
This field is mandatory when creating a visit. No blank names allowed!
descriptionstringA visit's description is free text. Gallagher's visitor management clients use it to store the visit's purpose. You can change it at any time.
receptionobjectrequiredEvery visit must have a reception. It represents the location at which your visitors will first arrive. Having one attached to a visit allows a kiosk at that location to hide visits for other receptions, revealing only those meant to start there.
Your choice of reception also determines the rules with which the visit must comply, because those rules come from the visitor management configuration on the reception's division.
Since every visit must have a reception, every POST that creates a visit must have one. Once you have a visit in the system you can change its reception, but be aware that changing receptions might change the visit's division, which also might change the rules to which the visit must conform, and if any validation fails--and there is a lot of it--the PATCH will fail.
The reception's name comes from the server in a GET, but you need not send it when creating or modifying a visit. Command Centre only needs an href from you.
Note that you must send the reception like it appears in the example, as an href inside a
block called reception. This will not work:
"reception": "https://localhost:8904/api/receptions/123"
visitorTypeobjectrequiredEvery visit must also have a visitor type. It provides an access group for your visitors so that they can have personal data (remember that a cardholder must be in an access group before he or she can have a PDF), and it is an index into the visitor management configuration that governs things like the host (the cardholder who is responsible for your visitors) and the access groups that the server will give your visitors when they sign in.
Use the href from the division's visitor management configuration, or the access group's href. Both will work.
You will receive the accessGroup object inside the visitorType block in a GET, but you
needn't send it in a POST or PATCH. The server can find the visitor type using only its href.
Like the reception, when you send this to the server you must use the same form as the
example, as an href inside a block called visitorType. The href can be to an access group
that you got from an access group or the
visitorTypes in the visitor management configuration on a
division. For example, both of these will
work as visitor type hrefs:
https://localhost:8904/api/access_groups/925 (taken from another visit, or a search of
access groups)
https://localhost:8904/api/divisions/2/visitor_types/925 (taken from a division config)
Your operator must have the privilege to view the access group, otherwise you will be told 'Access denied when writing to one or more fields'. Having the 'Modify Access Control' privilege on the group's division is a good choice because it not only gives you a view of access groups but the ability to change cardholders' membership of them, which you need to add visitors and visitor groups.
hostobjectrequiredA host is a cardholder, and every visit has one. A visit's host is the person who (optionally) receives an email or SMS notification when visitors start signing in, and is responsible for them until they sign out again.
A host must be a member of at least one of the visit's visitor type's host access groups
at the time that you create or modify the visit. To find out what those are, get the
visitor management configuration for your reception's division (remembering that your
visit's division is its reception's division). That visitor type contains an array of
access groups called hostAccessGroups. Your host cardholder must be a member of one of
those groups or their descendants.
When you GET a visit the server will send you the host cardholder's name, but when you create a visit with a POST or modify one with a PATCH, you needn't send the name back. Do so if you must, but it will not have an effect.
fromstring<date-time>requiredEvery visit has a validity period, defined by start and end times called from and
until.
Not only are they both required when you create a visit, but the server will insist that
until is later than from and that you followed the date-time rules.
You can use short forms such as 2021-04-01+12 and the server will fill in zeroes for the
parts you missed. But because the end time must always be later than the start time you
cannot use the same date for both start and end. The server will treat them both like
midnight, see that they are equal, and reject your API call. So if you have a one-day visit,
bump until by a day or pad it out until the evening. 2021-04-01T23:59:59+12, for example.
untilstring<date-time>requiredEvery visit has a validity period, defined by start and end times called from and
until.
Not only are they both required when you create a visit, but the server will insist that
until is later than from and that you followed the date-time rules.
You can use short forms such as 2021-04-01+12 and the server will fill in zeroes for the
parts you missed. But because the end time must always be later than the start time you
cannot use the same date for both start and end. The server will treat them both like
midnight, see that they are equal, and reject your API call. So if you have a one-day visit,
bump until by a day or pad it out until the evening. 2021-04-01T23:59:59+12, for example.
locationstringFree text. Entirely optional in the POST and PATCH.
visitorAccessGroupsArray<object>These are the access groups that the server will add your visitors to after they complete induction and sign in. They define the access that your visitors have to the site, as they do for all cardholders.
It is perfectly valid to have no visitor access groups on a visit - it just means that someone will be opening all your guests' doors for them. That might be the kind of thing they are used to anyway.
The visitor access groups on a visit must be a subset of the visitor access groups on the division's visitor type (in the list of visitor access groups in the visit's reception's division's visitor management configuration, in other words).
When you send a visitor access group block to the server in a POST, just send an array of blocks each containing an href to an access group. This is one reason why you cannot copy a visit by POSTing back the JSON from a GET.
When you send a visitor access group block in a PATCH to edit an existing visit, the server
expects it to contain one or two arrays called add and remove. The add array should
contain access group hrefs, exactly as you would send when creating a visit. The remove
array can contain access group hrefs or the hrefs you received in the GET. The PATCH example
shows two hrefs trying to remove the same access group.
Your operator must have a privilege on the groups' divisions that allows viewing the access groups and adding and removing cardholders to and from them, otherwise you will be told 'Access denied when writing to one or more fields'. 'Modify Access Control' is a good choice.
visitorsArray<object>When the server sends this array to you, it contains two hrefs for each cardholder due to
arrive in this visit. The first, outside the cardholder block, is the URL you
PATCH to change the state of their visit (new in
8.90). The one inside the cardholder block is simply an href to the cardholder item. You
can use either href when removing visitors from a visit with a PATCH.
The status block contains two fields. value is a human-readable description of their
state in the server's language. type comes from a fixed enumeration.
The invitation string is opaque: please do not interpret it.
Note that a visit with no visitors on it will come out of the API but will not appear in Gallagher's visitor management application. Why clutter the screen if you're not expecting anybody, after all.
When you send a visitor block to the server in a POST to create a visit, just send an array of
blocks each containing an href to a cardholder. You do not need to put the hrefs inside
cardholder blocks, as the server sends to you, so this is the other reason you cannot copy a
visit by POSTing back the JSON from a GET.
Also do not bother sending a status block: the server will ignore it and set all visitors
to 'expected'.
When you send a visitor block in a PATCH to edit an existing visit, the server expects it to
contain an array called add, an array called remove, or both. The add array should contain
cardholder hrefs, exactly as you would send when creating a visit. The remove array should
contain either of the hrefs you received for the cardholder in the GET. The PATCH example
shows two hrefs removing the same cardholder.
Your operator must have a privilege on the groups' divisions that allows viewing the access groups and adding and removing cardholders to and from them, otherwise you will be told 'Access denied when writing to one or more fields'. 'Modify Access Control' is a good choice.
Response
Success.
The body of the POST did not describe a valid visit. Check the body of the server's response for hints.
The site does not have both the RESTCardholders and VisitorManagement licences.
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/visits", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/visits") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/visits' \
-H 'Content-Type: application/json' \
-d '{
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger'\''s office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}'const response = await fetch('https://127.0.0.1:8904/api/visits', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}
response = requests.post('https://127.0.0.1:8904/api/visits', json=payload)
data = response.json(){
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1962-12-06T14:35:00Z",
"until": "2025-08-15T23:00:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
]
}Get details of a visit
This returns details for a visit. Follow the href in the visit item to get here rather than building the URL yourself.
All visit endpoints are new to 8.50.
Parameters
idstringrequiredpathAn internal identifier.
fieldsArray<string>hrefnamedescriptiondivisionserverDisplayNamenotesreceptionvisitorTypehostfromuntillocationbadgeTextvisitorAccessGroupsvisitorsvisitors.cardholdervisitors.hrefvisitors.invitationvisitors.status...defaultsdefaultsquerySpecifies the fields in the response. The values you can list are the same in the search and details pages. Using it you can return everything on the search page that you would find on the details page, plus the visit's notes. Separate values with commas.
Use the special value defaults to return the fields you would have received had you not
given the parameter at all. Add more after a comma.
Treat the string matches as case sensitive.
Response
Success.
The site does not have both the RESTCardholders and VisitorManagement licences.
That is not the URL of a visit, or it is a visit but the operator does not have the privilege to see ('View Visits' or 'Edit Visits').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://127.0.0.1:8904/api/visits/{id}", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://127.0.0.1:8904/api/visits/{id}"));
var data = await response.Content.ReadAsStringAsync();curl -X GET 'https://127.0.0.1:8904/api/visits/{id}'const response = await fetch('https://127.0.0.1:8904/api/visits/{id}', {
method: 'GET',
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits/{id}', {
method: 'GET',
});
const data: Record<string, unknown> = await response.json();import requests
response = requests.get('https://127.0.0.1:8904/api/visits/{id}')
data = response.json(){
"name": "Visit hosted by Greta Ginger",
"href": "https://localhost:8904/api/visits/941",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Initial scoping",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"reception": {
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/divisions/2/v_t/925",
"accessGroup": {
"name": "Visitor group 1",
"href": "https://localhost:8904/api/access_groups/925"
}
},
"host": {
"name": "Ginger Greeter",
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2021-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/visits/941/visitors/940",
"cardholder": {
"name": "Red Adair",
"href": "https://localhost:8904/api/cardholders/940"
},
"status": {
"value": "Expected Back",
"type": "expectedBack"
},
"invitation": "text to be encoded into a QR code for 940"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9040",
"cardholder": {
"name": "James Page",
"href": "https://localhost:8904/api/cardholders/9040"
},
"status": {
"value": "On-Site",
"type": "onSite"
},
"invitation": "text to be encoded into a QR code for 9040"
}
]
}Modify a visit
Changes an existing visit.
Follow the href in a visit item to get here rather than building the URL yourself.
New to 8.50.
Body
As well as simple attributes such as from and until, the PATCH body you send can
contain instructions for updating the lists of visitors and access groups.
This is a sample PATCH body that modifies a visit when sent to its href, /api/visits/941.
The differences from a POST are in the visitorAccessGroups and visitors arrays. In a POST
they are arrays of hrefs, but in a PATCH they must be objects. Each object should contain an
array called add and / or an array called remove. Each of those should contain hrefs to
add to or remove from the visit.
The other big difference between a POST and a PATCH is that all fields are optional in a PATCH.
namestringYou can name a visit however you like. Gallagher's in-house applications name them after their hosts.
This field is mandatory when creating a visit. No blank names allowed!
descriptionstringA visit's description is free text. Gallagher's visitor management clients use it to store the visit's purpose. You can change it at any time.
receptionobjectEvery visit must have a reception. It represents the location at which your visitors will first arrive. Having one attached to a visit allows a kiosk at that location to hide visits for other receptions, revealing only those meant to start there.
Your choice of reception also determines the rules with which the visit must comply, because those rules come from the visitor management configuration on the reception's division.
Since every visit must have a reception, every POST that creates a visit must have one. Once you have a visit in the system you can change its reception, but be aware that changing receptions might change the visit's division, which also might change the rules to which the visit must conform, and if any validation fails--and there is a lot of it--the PATCH will fail.
The reception's name comes from the server in a GET, but you need not send it when creating or modifying a visit. Command Centre only needs an href from you.
Note that you must send the reception like it appears in the example, as an href inside a
block called reception. This will not work:
"reception": "https://localhost:8904/api/receptions/123"
visitorTypeobjectEvery visit must also have a visitor type. It provides an access group for your visitors so that they can have personal data (remember that a cardholder must be in an access group before he or she can have a PDF), and it is an index into the visitor management configuration that governs things like the host (the cardholder who is responsible for your visitors) and the access groups that the server will give your visitors when they sign in.
Use the href from the division's visitor management configuration, or the access group's href. Both will work.
You will receive the accessGroup object inside the visitorType block in a GET, but you
needn't send it in a POST or PATCH. The server can find the visitor type using only its href.
Like the reception, when you send this to the server you must use the same form as the
example, as an href inside a block called visitorType. The href can be to an access group
that you got from an access group or the
visitorTypes in the visitor management configuration on a
division. For example, both of these will
work as visitor type hrefs:
https://localhost:8904/api/access_groups/925 (taken from another visit, or a search of
access groups)
https://localhost:8904/api/divisions/2/visitor_types/925 (taken from a division config)
Your operator must have the privilege to view the access group, otherwise you will be told 'Access denied when writing to one or more fields'. Having the 'Modify Access Control' privilege on the group's division is a good choice because it not only gives you a view of access groups but the ability to change cardholders' membership of them, which you need to add visitors and visitor groups.
hostobjectA host is a cardholder, and every visit has one. A visit's host is the person who (optionally) receives an email or SMS notification when visitors start signing in, and is responsible for them until they sign out again.
A host must be a member of at least one of the visit's visitor type's host access groups
at the time that you create or modify the visit. To find out what those are, get the
visitor management configuration for your reception's division (remembering that your
visit's division is its reception's division). That visitor type contains an array of
access groups called hostAccessGroups. Your host cardholder must be a member of one of
those groups or their descendants.
When you GET a visit the server will send you the host cardholder's name, but when you create a visit with a POST or modify one with a PATCH, you needn't send the name back. Do so if you must, but it will not have an effect.
fromstring<date-time>Every visit has a validity period, defined by start and end times called from and
until.
Not only are they both required when you create a visit, but the server will insist that
until is later than from and that you followed the date-time rules.
You can use short forms such as 2021-04-01+12 and the server will fill in zeroes for the
parts you missed. But because the end time must always be later than the start time you
cannot use the same date for both start and end. The server will treat them both like
midnight, see that they are equal, and reject your API call. So if you have a one-day visit,
bump until by a day or pad it out until the evening. 2021-04-01T23:59:59+12, for example.
untilstring<date-time>Every visit has a validity period, defined by start and end times called from and
until.
Not only are they both required when you create a visit, but the server will insist that
until is later than from and that you followed the date-time rules.
You can use short forms such as 2021-04-01+12 and the server will fill in zeroes for the
parts you missed. But because the end time must always be later than the start time you
cannot use the same date for both start and end. The server will treat them both like
midnight, see that they are equal, and reject your API call. So if you have a one-day visit,
bump until by a day or pad it out until the evening. 2021-04-01T23:59:59+12, for example.
locationstringFree text. Entirely optional in the POST and PATCH.
visitorAccessGroupsobjectThese are the access groups that the server will add your visitors to after they complete induction and sign in. They define the access that your visitors have to the site, as they do for all cardholders.
It is perfectly valid to have no visitor access groups on a visit - it just means that someone will be opening all your guests' doors for them. That might be the kind of thing they are used to anyway.
The visitor access groups on a visit must be a subset of the visitor access groups on the division's visitor type (in the list of visitor access groups in the visit's reception's division's visitor management configuration, in other words).
When you send a visitor access group block to the server in a POST, just send an array of blocks each containing an href to an access group. This is one reason why you cannot copy a visit by POSTing back the JSON from a GET.
When you send a visitor access group block in a PATCH to edit an existing visit, the server
expects it to contain one or two arrays called add and remove. The add array should
contain access group hrefs, exactly as you would send when creating a visit. The remove
array can contain access group hrefs or the hrefs you received in the GET. The PATCH example
shows two hrefs trying to remove the same access group.
Your operator must have a privilege on the groups' divisions that allows viewing the access groups and adding and removing cardholders to and from them, otherwise you will be told 'Access denied when writing to one or more fields'. 'Modify Access Control' is a good choice.
visitorsobjectWhen the server sends this array to you, it contains two hrefs for each cardholder due to
arrive in this visit. The first, outside the cardholder block, is the URL you
PATCH to change the state of their visit (new in
8.90). The one inside the cardholder block is simply an href to the cardholder item. You
can use either href when removing visitors from a visit with a PATCH.
The status block contains two fields. value is a human-readable description of their
state in the server's language. type comes from a fixed enumeration.
The invitation string is opaque: please do not interpret it.
Note that a visit with no visitors on it will come out of the API but will not appear in Gallagher's visitor management application. Why clutter the screen if you're not expecting anybody, after all.
When you send a visitor block to the server in a POST to create a visit, just send an array of
blocks each containing an href to a cardholder. You do not need to put the hrefs inside
cardholder blocks, as the server sends to you, so this is the other reason you cannot copy a
visit by POSTing back the JSON from a GET.
Also do not bother sending a status block: the server will ignore it and set all visitors
to 'expected'.
When you send a visitor block in a PATCH to edit an existing visit, the server expects it to
contain an array called add, an array called remove, or both. The add array should contain
cardholder hrefs, exactly as you would send when creating a visit. The remove array should
contain either of the hrefs you received for the cardholder in the GET. The PATCH example
shows two hrefs removing the same cardholder.
Your operator must have a privilege on the groups' divisions that allows viewing the access groups and adding and removing cardholders to and from them, otherwise you will be told 'Access denied when writing to one or more fields'. 'Modify Access Control' is a good choice.
Parameters
idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid change to a visit. Check the body of the server's response for hints.
If you receive 'No fields have been defined for update', check that your submission body is valid JSON.
The site does not have the RESTCardholders and VisitorManagement licences, or the operator does not have the privilege to modify that visit ('Edit Visits'), or you attempted to set a field for which you have no privilege, or the visit has expired. See the descriptions for each field for some rules about their use.
That is not the URL of a visit, or it is a visit that the operator does not have the privilege to see ('View Visits' or 'Edit Visits').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/visits/{id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/visits/{id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/visits/{id}' \
-H 'Content-Type: application/json' \
-d '{
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger'\''s office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}'const response = await fetch('https://127.0.0.1:8904/api/visits/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits/{id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}
response = requests.patch('https://127.0.0.1:8904/api/visits/{id}', json=payload)
data = response.json(){
"name": "Visit hosted by Greta Ginger",
"description": "Initial scoping",
"reception": {
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/access_groups/925"
},
"host": {
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2023-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": {
"add": [
{
"href": "https://localhost:8904/api/access_groups/926"
},
{
"href": "https://localhost:8904/api/access_groups/9260"
}
],
"remove": [
{
"href": "https://localhost:8904/api/access_groups/930"
},
{
"href": "https://localhost:8904/api/visits/941/visitor_access_groups/930"
}
]
},
"visitors": {
"add": [
{
"href": "https://localhost:8904/api/cardholders/940"
},
{
"href": "https://localhost:8904/api/cardholders/9040"
}
],
"remove": [
{
"href": "https://localhost:8904/api/cardholders/937"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9370"
}
]
}
}Modify a visitor's status
Changes a visitor's state for a visit. This is how you mark a visitor as signing in, signed in, on site, off site again, etc.
Doing so will create an event if your visitor was not already in the state you attempted to move them to.
It will not send a notification to the visitor's host or escort. There are other API calls for notifying that a visitor is signing in or on-site.
Note that this URL is different from a normal cardholder href because a cardholder can be a visitor on more than one visit at a time.
Do not code this URL into your application. Take it from the results of a visit search.
New to 8.90.
Body
This must contain a block called status containing a string called value, giving your
desired state of the visitor.
This is a sample PATCH body that marks a visitor as signing in.
statusobjectThis is the same status that you see in a visit GET for each visitor
except that you only need to send the value field.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a valid change to a visitor's status. Check the body of the server's response for hints.
The site does not have the RESTCardholders and VisitorManagement licences, or the operator does not have the privilege to modify that visit ('Edit Visits'), or you attempted to set a field for which you have no privilege, or the visit has expired.
That is not the URL of a visitor, or it is a visitor on a visit that the operator does not have the privilege to see ('View Visits' or 'Edit Visits').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"status": {
"value": "signingIn"
}
}`)
req, _ := http.NewRequest("PATCH", "https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"status": {
"value": "signingIn"
}
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Patch, "https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X PATCH 'https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}' \
-H 'Content-Type: application/json' \
-d '{
"status": {
"value": "signingIn"
}
}'const response = await fetch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"status": {
"value": "signingIn"
}
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"status": {
"value": "signingIn"
}
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"status": {
"value": "signingIn"
}
}
response = requests.patch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}', json=payload)
data = response.json(){
"status": {
"value": "signingIn"
}
}Notify that a visitor is signing in
Sends an email and / or SMS notification to a visit's host.
The difference between this and notify_onsite is the type of event that Command Centre puts in the audit trail. In all other ways they are identical.
New to 8.90.
Body
The contents of the notification email or SMS, or both.
This is a sample body for the methods that send notifications to a visit's host. Call them as a visitor moves through their reception process.
Command Centre can send email, an SMS, or both. It will send email if you give it either the subject or a message body. It will send a TXT only if you give it the SMS message.
emailSubjectstringIf you wish to send an email notification, put its subject here. Email will go out if you leave it blank but provide a body, but it is generally easier on the recipient the other way around.
emailMessagestringIf you want your email notification to contain body text, put it here.
smsMessagestringIf you wish to send an SMS notification (a TXT), put it here. Leave it blank if you only wish to send email.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a notification. Check the body of the server's response for hints.
The site does not have the RESTCardholders and VisitorManagement licences, or the operator does not have sufficient privileges, or the visit has expired.
That is not a visitor's notification URL, or it is a notification URL for a visitor on a visit that the operator does not have the privilege to see ('View Visits' or 'Edit Visits').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_signing_in", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_signing_in") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_signing_in' \
-H 'Content-Type: application/json' \
-d '{
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}'const response = await fetch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_signing_in', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_signing_in', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}
response = requests.post('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_signing_in', json=payload)
data = response.json(){
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}Notify that a visitor is on-site
Sends an email and / or SMS notification to a visit's host.
The difference between this and notify_signing_in is the type of event that Command Centre puts in the audit trail. In all other ways they are identical.
New to 8.90.
Body
The contents of the notification email or SMS, or both.
This is a sample body for the methods that send notifications to a visit's host. Call them as a visitor moves through their reception process.
Command Centre can send email, an SMS, or both. It will send email if you give it either the subject or a message body. It will send a TXT only if you give it the SMS message.
emailSubjectstringIf you wish to send an email notification, put its subject here. Email will go out if you leave it blank but provide a body, but it is generally easier on the recipient the other way around.
emailMessagestringIf you want your email notification to contain body text, put it here.
smsMessagestringIf you wish to send an SMS notification (a TXT), put it here. Leave it blank if you only wish to send email.
Parameters
idstringrequiredpathAn internal identifier.
secondary_idstringrequiredpathAn internal identifier.
Response
Success. Future versions will return feedback from the server about your PATCH.
Success.
The body of the PATCH did not describe a notification. Check the body of the server's response for hints.
The site does not have the RESTCardholders and VisitorManagement licences, or the operator does not have sufficient privileges, or the visit has expired.
That is not a visitor's notification URL, or it is a notification URL for a visitor on a visit that the operator does not have the privilege to see ('View Visits' or 'Edit Visits').
Authorization
API_keyapiKey in headerClients authenticate by including a pre-shared API key in the Authorization header of each
request after an authorisation method of GGL-API-KEY:
Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB
For a fuller description see Access control.
basichttp (basic)This is HTTP Basic authentication using a blank username and the API key as the password.
Example: Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==
That example is equivalent to the example in the description of the api_Key method:
prepending a colon to the API key beginning with C774 then encoding it into Base64
produces the 56 characters beginning with OkM3.
The colon is a separator between a username, which is blank in our case, and the password, which is our API key.
For a fuller description see Access control.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}`)
req, _ := http.NewRequest("POST", "https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite", body)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}using var client = new HttpClient();
var content = new StringContent("""
{
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}""", System.Text.Encoding.UTF8, "application/json");
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite") { Content = content });
var data = await response.Content.ReadAsStringAsync();curl -X POST 'https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite' \
-H 'Content-Type: application/json' \
-d '{
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}'const response = await fetch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}),
});
const data = await response.json();const response = await fetch('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}),
});
const data: Record<string, unknown> = await response.json();import requests
payload = {
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}
response = requests.post('https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite', json=payload)
data = response.json(){
"emailSubject": "Visitor arriving",
"emailMessage": "",
"smsMessage": "Visitor arriving"
}Models
AccessGroupCommonFields
objectThese are the fields common to the access group summary, details, POST, and PATCH.
namestringAll items have a name. Command Centre makes one up for you if you omit it when creating an item.
descriptionstringdivisionobjectMandatory when creating any access group. In this example, we want the access group in division 352.
parentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
}
}AccessGroupSummary
objectThese are the fields in the access group summary.
These are the fields common to the access group summary, details, POST, and PATCH.
namestringAll items have a name. Command Centre makes one up for you if you omit it when creating an item.
descriptionstringdivisionobjectMandatory when creating any access group. In this example, we want the access group in division 352.
parentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
idstringread onlyAn alphanumeric identifier for this access group. No API calls use access group IDs.
hrefstring<uri-reference>read onlyA link to an access group detail object for this access group.
notesstringcardholdersobjectread onlyFollowing this link lists the group's direct memberships.
In v8.00 you will receive this field along with the ID and href in an access group's details page whether or not you specified it in the fields parameter, but if you send the fields parameter to 8.10 you will only get what you asked for.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"id": "352",
"href": "https://host.com:8904/api/access_groups/352",
"notes": "",
"cardholders": {
"href": "https://host.com:8904/api/access_groups/352/cardholders"
},
"serverDisplayName": "ruatoria.satellite.int"
}AccessGroupWriteableFields
objectThese are the fields common to the POST and PATCH.
These are the fields common to the access group summary, details, POST, and PATCH.
namestringAll items have a name. Command Centre makes one up for you if you omit it when creating an item.
descriptionstringdivisionobjectMandatory when creating any access group. In this example, we want the access group in division 352.
parentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
notesstringparentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
membershipAutoRemoveExpiredbooleanIf true, memberships will be removed when their until time passes. If false,
they will remain on the cardholder, inactive.
membershipFromDefaultstring<date-time>When a cardholder is granted membership of this access group without specifying a 'from' time, this default will apply. If left blank, such a membership will not have a 'from' time. It will be effective immediately, provided its 'until' time is blank or in the future.
To clear it, send the blank string "".
membershipUntilDefaultstring<date-time>When a cardholder is granted membership of this access group without specifying an 'until' time, this default will apply. If blank, such a membership will not have an 'until' time. It will be effective until removed.
To clear it, send the blank string "".
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"notes": "",
"membershipAutoRemoveExpired": true,
"membershipFromDefault": "2025-01-01T00:00:00Z",
"membershipUntilDefault": "2025-01-01T00:00:00Z"
}AccessGroupPDFsPOST
objectThis is where you give PDFs to an access group. All the members of the access group will be able to have values for the PDFs you list in this array.
Each element should contain the href of a PDF definition. You can take those from the PDFs
controller or other API routes, including the personalDataDefinitions block of another
access group.
The GET returns the names of the PDFs, but the POST will ignore them since these API calls are about access groups. If you need to change the name of a PDF, use that controller.
personalDataDefinitionsArray<object>{
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
]
}AccessGroupSearch
objectAn array of access group summaries, described in the next section, and a next link for more.
resultsArray<AccessGroupSummary>An array of access group summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"id": "352",
"href": "https://host.com:8904/api/access_groups/352",
"notes": "",
"cardholders": {
"href": "https://host.com:8904/api/access_groups/352/cardholders"
},
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://host.com:8904/api/access_groups?skip=61320"
}
}AccessGroupDetail
object/api/access_groups/{id} returns one of these. In addition to the basic details, it lists the child groups, privileges, and access zones and Salto items to which the group grants access.
There are brief descriptions of those fields below. If they fall short the Configuration Client's online documentation is the authority, in particular the section 'Setting up Access Groups'.
These are the fields in the access group summary.
These are the fields common to the access group summary, details, POST, and PATCH.
namestringAll items have a name. Command Centre makes one up for you if you omit it when creating an item.
descriptionstringdivisionobjectMandatory when creating any access group. In this example, we want the access group in division 352.
parentobjectA link to the group's parent.
This field is optional in a POST because unlike divisions, which must have a parent, an access group can stand alone. But should you wish it to be a member of another, link it here.
An access group may be a member of only one other: multiple inheritance is not possible.
Use the blank string "" to clear a group's parent in a PATCH.
idstringread onlyAn alphanumeric identifier for this access group. No API calls use access group IDs.
hrefstring<uri-reference>read onlyA link to an access group detail object for this access group.
notesstringcardholdersobjectread onlyFollowing this link lists the group's direct memberships.
In v8.00 you will receive this field along with the ID and href in an access group's details page whether or not you specified it in the fields parameter, but if you send the fields parameter to 8.10 you will only get what you asked for.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
This is where you give PDFs to an access group. All the members of the access group will be able to have values for the PDFs you list in this array.
Each element should contain the href of a PDF definition. You can take those from the PDFs
controller or other API routes, including the personalDataDefinitions block of another
access group.
The GET returns the names of the PDFs, but the POST will ignore them since these API calls are about access groups. If you need to change the name of a PDF, use that controller.
personalDataDefinitionsArray<object>childrenArray<object>Names and links for the groups that claim this one as a parent. This array does not include the childrens' children.
Notice of breaking change. This field being present and empty when the group has
no children is a break from the API's principle of omitting empty fields, and is
therefore a bug. A future version of Command Centre will not return the children
array if the group has no children.
visitorbooleanIf true, members of an access group with the 'escortVisitors' privilege can escort members of this group through a door, provided both groups have access to the entry zone.
A group with 'visitor' cannot also have 'escortVisitors', 'lockUnlockAccessZones', or 'firstCardUnlock', because visitors are not allowed to do those things.
New to 8.40.
escortVisitorsbooleanIf true, members of this group can escort members of a group with the 'visitor' privilege through a door, provided both groups have access to the entry zone.
A group cannot have both 'escortVisitors' and 'visitor'.
New to 8.40.
lockUnlockAccessZonesbooleanIf true, members of this group can use a reader or terminal to change the access mode of this group's access zones. They can do this by logging on, if the reader has a screen and keypad, or by double-badging.
A group cannot have both 'lockUnlockAccessZones' and 'visitor'.
New to 8.40.
enterDuringLockdownbooleanIf true, members of this group are not subject to lockdown restrictions when requesting to enter its access zones.
New to 8.40.
firstCardUnlockbooleanIf true, members of this group will change an access zone from secure to free when entering it, unlocking all its doors.
A group cannot have both 'firstCardUnlock' and 'visitor'.
New to 8.40.
overrideAperioPrivacybooleanSome Aperio locks have a 'privacy mode' button that room occupants can push when they do not want anyone else coming in. If this field is true, Aperio locks will ignore that button when members of this group attempt to open them.
This field will not appear if your site is not licensed for Aperio.
New to 8.40.
aperioOfflineAccessbooleanAperio locks normally refuse to open when offline. If this field is true, Aperio locks that support it will make an exception for members of this group.
Obviously the lock needs to be online long enough to synchronise this setting and the members of the group before setting it will have an effect.
This field will not appear if your site is not licensed for Aperio.
New to 8.40.
disarmAlarmZonesbooleanIf true, members of this group can use a reader or terminal to disarm the group's access zones' alarm zones, either by logging on or double-badging.
New to 8.40.
armAlarmZonesbooleanIf true, members of this group can use a reader or terminal to arm the group's access zones' alarm zones.
New to 8.40.
hvLfFenceZonesbooleanIf true, members of this group can use a reader or terminal to change the group's fence zones between 'high voltage' and 'low feel', which will in turn change the exuberance of their energisers. A group's fence zones are those that use the same alarm zones as the group's access zones.
New to 8.40.
viewAlarmsbooleanIf true, members of this group can view alarms and inputs on remote arming terminals ("RATs") and HBUS terminals.
New to 8.40.
shuntbooleanIf true, members of this group can shunt (isolate) items using RATs and HBUS terminals.
New to 8.40.
lockOutFenceZonesbooleanIf true, members of this group can lock out (de-energise, make safe) fence zones using RATs and HBUS terminals. Like all other access group privileges, this only works on the fence zones to which this group has access.
New to 8.40.
cancelFenceZoneLockoutbooleanNormally, only the cardholder who locked out a fence zone can cancel the lockout and re-energise the fence. With this privilege, members of this group can cancel any lockout on the group's fence zones.
New to 8.40.
ackAllbooleanIf true, members of this group can acknowledge alarms at a RAT or HBUS terminal.
An access group cannot have both this privilege and 'ackBelowHigh'.
New to 8.40.
ackBelowHighbooleanIf true, members of this group can acknowledge alarms at a RAT or HBUS terminal, provided the alarms are not at high, very high, or critical priority.
An access group cannot have both this privilege and 'ackAll'.
New to 8.40.
selectAlarmZonebooleanIf true, members of this group can choose from a list of the group's alarm zones when performing overrides at RATs and HBUS terminals, rather than having it chosen for them by site configuration.
A group can only have this privilege if it also has 'disarmAlarmZones' or 'armAlarmZones'. Without one of those there is no point in being able to select an alarm zone.
New to 8.40.
armWhileAlarmbooleanNormally, a cardholder cannot arm an alarm zone if it has open, unshunted, inputs. With this privilege, members of the group can force-arm the alarm zone from a RAT or HBUS terminal. What happens then depends on an alarm zone setting.
A group with this privilege will also have 'armAlarmZones' and will not have 'armWhileActiveAlarm'.
New to 8.40.
armWhileActiveAlarmbooleanNormally, a cardholder cannot arm an alarm zone when it has active alarms. Members of a group with this privilege can do so from an HBUS terminal, provided they also meet other criteria (detailed in the Configuration Client documentation).
A group with this privilege will also have have 'armAlarmZones' and will not have 'armWhileAlarm'.
New to 8.40.
isolateAlarmZonesbooleanMembers of a group with this privilege have the option of isolating open inputs from a RAT or HBUS terminal when they are preventing an alarm zone from arming. Like all these privileges, it only works for the alarm zones on the group's access zones.
To have this privilege, a group must also have 'armAlarmZones'.
New to 8.40.
accessArray<object>read onlyNames and hrefs of the access zones to which this access group gives access, and the schedules that govern it.
This field is read-only: you cannot yet change an access group's access via the API.
Your operator needs 'View Schedules' to see schedule hrefs, and 'View Site', 'Edit Site', or 'Override' to see access zone hrefs.
New to 8.40.
saltoAccessArray<object>read onlyTypes, names, and hrefs of the Salto doors and door groups ("Salto Access Zones") to which this access group gives access, and the schedules that govern it.
Watch those definitions: a 'Salto Access Zone' is a group of Salto doors, while Command Centre's definition of an access zone is a space into which a cardholder moves after passing through a door.
Therefore if an access group gives access to a Salto Access Zone, it is giving access through any number of Salto doors. If you don't have access to the Salto system itself you can see the Salto zone/door hierarchy in the Command Centre Configuration client.
New to 8.40.
alarmZonesArray<object>Names and hrefs of the alarm zones to which members of this access group have the 20-odd management privileges listed above.
Each connection between the access group and the alarm zone is in its own block called
alarmZone to leave room for the addition of more fields later.
Added in 8.40.
{
"name": "R&D special projects group.",
"description": "Deep underground.",
"division": {
"href": "https://host.com:8904/api/divisions/352"
},
"parent": {
"href": "https://host.com:8904/api/access_groups/100"
},
"id": "352",
"href": "https://host.com:8904/api/access_groups/352",
"notes": "",
"cardholders": {
"href": "https://host.com:8904/api/access_groups/352/cardholders"
},
"serverDisplayName": "ruatoria.satellite.int",
"personalDataDefinitions": [
{
"name": "email",
"href": "https://host.com:8904/api/personal_data_fields/5516"
},
{
"name": "cell",
"href": "https://host.com:8904/api/personal_data_fields/9370"
}
],
"children": [
{
"href": "https://host.com:8904/api/access_groups/5122",
"name": "R&D super-special projects"
},
{
"href": "https://host.com:8904/api/access_groups/3420",
"name": "R&D social committee"
}
],
"visitor": false,
"escortVisitors": false,
"lockUnlockAccessZones": false,
"enterDuringLockdown": false,
"firstCardUnlock": false,
"overrideAperioPrivacy": false,
"aperioOfflineAccess": false,
"disarmAlarmZones": false,
"armAlarmZones": false,
"hvLfFenceZones": false,
"viewAlarms": false,
"shunt": false,
"lockOutFenceZones": false,
"cancelFenceZoneLockout": false,
"ackAll": false,
"ackBelowHigh": false,
"selectAlarmZone": false,
"armWhileAlarm": false,
"armWhileActiveAlarm": false,
"isolateAlarmZones": false,
"access": [
{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/333",
"name": "Twilight zone"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/5",
"name": "Default Cardholder Access Granted"
}
},
{
"accessZone": {
"href": "https://host.com:8904/api/access_zones/412",
"name": "Server room"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/557",
"name": "8am-5pm weekdays"
}
}
],
"saltoAccess": [
{
"saltoItemType": {
"value": "saltoAccessZone"
},
"saltoItem": {
"href": "https://host.com:8904/api/items/570",
"name": "Salto BLE CV19"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/5",
"name": "Default Cardholder Access Granted"
}
},
{
"saltoItemType": {
"value": "saltoDoor"
},
"saltoItem": {
"href": "https://host.com:8904/api/items/579",
"name": "Salto CU5000"
},
"schedule": {
"href": "https://host.com:8904/api/schedules/557",
"name": "8am-5pm weekdays"
}
}
],
"alarmZones": [
{
"alarmZone": {
"href": "https://host.com:8904/api/alarm_zones/328",
"name": "Roswell building 2 lobby alarms"
}
},
{
"alarmZone": {
"href": "https://host.com:8904/api/alarm_zones/10138",
"name": "Roswell building 3 lobby alarms"
}
}
]
}AccessGroupMembership
objectReturned in an array by /api/access_groups/{id}/cardholders, containing cardholders who are direct members of a particular group. The array does not contain the group's child groups, or their cardholder members.
Each item contains a cardholder and (possibly) two date-times. The group membership is active if and only if the current time is between 'from' and 'until'. If 'from' is absent, assume the distant past. If 'until' is absent, assume the far future.
Use the href in the cardholder block to change the 'from' and 'until', or even the group, using cardholder patch.
Each also contains an href at the top level: DELETE that to remove the membership.
hrefstring<uri-reference>DELETE this URL to remove the membership. Do not specify this when creating or modifying a cardholder.
DELETE is the only verb you can use on this URL. GET will always return a 404.
cardholderobjectThe name and href of the member cardholder.
fromstring<time-stamp>untilstring<time-stamp>{
"href": "https://host.com:8904/api/cardholders/325/access_groups/D714D8A894724F",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://host.com:8904/api/cardholders/325"
},
"from": "2017-01-01T00:00:00Z",
"until": "2017-12-31T11:59:59Z"
}AccessZoneSummary
object/api/access_zones returns an array of these. It is a subset of what you get from a
access zone's detail page linked as the href in this object.
hrefstring<uri-reference>A link to an access zone detail object for this access zone. It is also Command Centre's identifier for this access zone: use it whenever you need to specify an access zone in REST operations.
idstringAn alphanumeric identifier, unique to the server.
This is the ID to use in the source parameter of event
filters if you want to limit your events to
particular access zones.
namestring{
"href": "https://localhost:8904/api/access_zones/3280",
"id": "3280",
"name": "Roswell building 2 lobby"
}AccessZoneDetail
object/api/access_zones/{id} returns one of these.
As well as the properties below, it contain a block called doors. This is reserved for
future development and its behaviour could change in later versions of Command Centre.
/api/access_zones returns an array of these. It is a subset of what you get from a
access zone's detail page linked as the href in this object.
hrefstring<uri-reference>A link to an access zone detail object for this access zone. It is also Command Centre's identifier for this access zone: use it whenever you need to specify an access zone in REST operations.
idstringAn alphanumeric identifier, unique to the server.
This is the ID to use in the source parameter of event
filters if you want to limit your events to
particular access zones.
namestringdescriptionstringdivisionobjectThe division containing this Access Zone.
doorsArray<object>A list containing names of and links to the doors that control entry to this access zone.
zoneCountintegerThe number of cardholders in the zone, according to its zone counting configuration.
An access zone's count is part of its state, so all that reading applies here too. The authoritative source is the zone's hardware controller, so the REST server only has it if it is monitoring it for another reason. If it is not, it returns a zero.
That is why zone counts are not in the default set of fields. You can ask for them
using the 'fields' parameter, but you risk receiving a zero, so the recommended way is
to follow the Access Zone's 'updates' link with fields=defaults,zoneCount appended
after the appropriate query parameter separator.
A zone count is correct iff the access zone is online: the status flags must contain one of 'secure', 'dualAuth', 'codeOrCard', or 'free'. Even then, the zone count could be a minute out of date (depending on a server property that determines how long a hardware item can be silent before the server calls it offline).
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
updatesobjectFollow the URL in the href inside this block to receive the item's current status, then follow
the next link in the results to long poll for changes to that status.
This method only monitors one item at a time. To monitor the status of many items, use the status-monitoring routes.
Update pages take the same fields parameter as summary and details pages. You should use
that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's status
is unknown until something is monitoring it. If you want status flags on the search and
details pages you must ask for them using the fields parameter, but our advice is to monitor
them using status subscriptions if you are running 8.30 or
later, otherwise the item's updates link. See the item status section
for a full description of how to stay up to date with item status, and this item's
introduction in the Operations section for what flags this item might return and what they
mean.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
commandsobjectA block of commands, each represented by a block containing an href that accepts a POST that will send an override to the access zone, changing its state.
It will be missing if your operator does not have a privilege that allows overriding the access zone (examples of which are in the documentation for the POSTs).
{
"href": "https://localhost:8904/api/access_zones/3280",
"id": "3280",
"name": "Roswell building 2 lobby",
"description": "Receives all visitors.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"doors": [
{
"name": "Front door",
"href": "https://localhost:8904/api/doors/332"
},
{
"name": "West stairwell lobby door",
"href": "https://localhost:8904/api/doors/745"
}
],
"zoneCount": 365,
"notes": "Multi-line text...",
"shortName": "Short text",
"updates": {
"href": "https://localhost:8904/api/access_zones/3280/updates/0_0_0"
},
"statusFlags": [
"secure"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"free": {
"href": "https://localhost:8904/api/access_zones/333/free"
},
"freeUntil": {
"href": "https://localhost:8904/api/access_zones/333/free"
},
"freePin": {
"href": "https://localhost:8904/api/access_zones/333/free_pin"
},
"freePinUntil": {
"href": "https://localhost:8904/api/access_zones/333/free_pin"
},
"secure": {
"href": "https://localhost:8904/api/access_zones/333/secure"
},
"secureUntil": {
"href": "https://localhost:8904/api/access_zones/333/secure"
},
"securePin": {
"href": "https://localhost:8904/api/access_zones/333/secure_pin"
},
"securePinUntil": {
"href": "https://localhost:8904/api/access_zones/333/secure_pin"
},
"codeOnly": {
"href": "https://localhost:8904/api/access_zones/333/code_only"
},
"codeOnlyUntil": {
"href": "https://localhost:8904/api/access_zones/333/code_only"
},
"codeOnlyPin": {
"href": "https://localhost:8904/api/access_zones/333/code_only_pin"
},
"codeOnlyPinUntil": {
"href": "https://localhost:8904/api/access_zones/333/code_only_pin"
},
"dualAuth": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth"
},
"dualAuthUntil": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth"
},
"dualAuthPin": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth_pin"
},
"dualAuthPinUntil": {
"href": "https://localhost:8904/api/access_zones/333/dual_auth_pin"
},
"forgiveAntiPassback": {
"href": "https://localhost:8904/api/access_zones/333/forgive_anti_passback"
},
"setZoneCount": {
"href": "https://localhost:8904/api/access_zones/333/set_zone_count"
},
"lockDown": {
"href": "https://localhost:8904/api/access_zones/333/lock_down"
},
"cancelLockDown": {
"href": "https://localhost:8904/api/access_zones/333/cancel_lock_down"
},
"cancel": {
"href": "https://localhost:8904/api/access_zones/333/cancel"
}
}
}AlarmSearch
objectAn array of alarm summaries, and either a 'next' or an 'updates' link you should follow to retrieve more alarms.
alarmsArray<AlarmSummary>An array of alarm summaries.
nextobjectThe link to the next page of alarms. Missing if you have retrieved all the current alarms.
updatesobjectThe link to follow to long-poll for alarm changes. Missing if you have not yet retrieved all the current alarms.
{
"alarms": [
{
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}
],
"next": {
"href": "https://localhost:8904/api/alarms?start=92143&pos=61320"
},
"updates": {
"href": "https://localhost:8904/api/alarms/updates?id=92143.1"
}
}AlarmUpdates
objectupdatesArray<AlarmSummary>An array of summaries of alarms created or modified since the previous call.
nextobjectFollow this link to perform another long poll.
{
"updates": [
{
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}
],
"next": {
"href": "https://localhost:8904/api/alarms/updates?id=10135"
}
}AlarmSummary
object/api/alarms returns an array of these, and /api/alarms/{id} returns one with more fields.
hrefstring<uri-reference>A link to the details of this alarm.
idstringAn alphanumeric identifier for this alarm, unique to the server.
timestringThe time the alarm occurred.
messagestringThe alarm's message.
sourceobjectThe ID and href are new to 8.10.
typestringThe name of this alarm's event type.
Deprecated in favour of the eventType block.
eventTypeobjectID and name of the event's or alarm's type. There is a long list of them at /events/groups.
Unlike the type field, this has the same format in an event as it does in an alarm.
Added in 8.90. Because it is a new field, it does not appear by default. Ask for it using
the fields parameter.
priorityinteger[0, 9]Numeric priority. 9 is critical and 0 is not an event.
statestringunacknowledgedacknowledgedprocessedThe state of the alarm. Alarms start at "unacknowledged".
activebooleanAlarms can be stateless or stateful. Stateless alarm types are spikes: an operator getting their password wrong or a cardholder being denied at a door. Stateful alarms have a triggering event and a restoring event: a door being forced open then closed, or a service going offline then reappearing later.
Stateless alarms are always inactive: active will be false. Stateful alarms will
have active true between being raised and restored.
An operator cannot process (dismiss) an active alarm without the 'Force process' privilege.
divisionobjectThe division entity representing the division of the alarm. GET the href for full details.
eventobjectA block containing a link ("href") to the details page of this alarm's event. Useful if you want an alarm's related items.
This mirrors an event's alarm field, which comes the other way.
Added in 8.90. Not returned by default - you need to ask for it using the fields query parameter.
notePresetsArray<string>Preset alarm notes to use for this specific alarm. Missing if the alarm does not have specific notes; in this case the client should fall back to the server defaults.
viewobjectPOST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.
commentobjectPOST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.
acknowledgeobjectPOST an alarm update request JSON object to the href to acknowledge an alarm. Missing if the system has mandatory alarm notes on for this alarm, or if the alarm is already acknowledged.
acknowledgeWithCommentobjectPOST an alarm update request JSON object to the href to acknowledge an alarm and place an alarm note against it. Missing if the alarm is already acknowledged.
processobjectPOST an alarm update request JSON object to the href to process an alarm. Missing if the system has mandatory alarm notes on for this alarm. Missing if the alarm cannot be processed (if it is active, for example).
processWithCommentobjectPOST an alarm update request JSON object to the href to process an alarm and place an alarm note against it. Missing if the alarm cannot be processed (if it is active, for example).
forceProcessobjectPOST an alarm update request JSON object to this href to process an active alarm. Missing
if the alarm is not active (in which case you will have process and processWithComment
links instead).
This action can take a comment in the body. In fact an explanation is recommended since force-processing an alarm is unusual.
{
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}AlarmDetail
object/api/alarms/{id} returns one of these. It contains everything from the alarm summary results, plus some extra fields that are too expensive to compute and return for large result sets.
detailsstringThe full alarm details text. This may be up to 2048 UTF-8 characters, each of which could (theoretically) be four bytes long.
historyArray<AlarmHistoryEntry>An array of alarm history entries. Missing if there is no history.
instructionobjectGET the href it contains to retrieve the alarm instruction body inside an HTML document.
In 9.10 and later you can request the instruction's name using the query parameter
fields=instruction.name. If you want the href as well, add ,instruction.href.
Missing if there is no alarm instruction for this alarm.
cardholderobjectThe cardholder entity associated with this alarm. GET the href for full details. Missing if this alarm does not have an associated cardholder.
/api/alarms returns an array of these, and /api/alarms/{id} returns one with more fields.
hrefstring<uri-reference>A link to the details of this alarm.
idstringAn alphanumeric identifier for this alarm, unique to the server.
timestringThe time the alarm occurred.
messagestringThe alarm's message.
sourceobjectThe ID and href are new to 8.10.
typestringThe name of this alarm's event type.
Deprecated in favour of the eventType block.
eventTypeobjectID and name of the event's or alarm's type. There is a long list of them at /events/groups.
Unlike the type field, this has the same format in an event as it does in an alarm.
Added in 8.90. Because it is a new field, it does not appear by default. Ask for it using
the fields parameter.
priorityinteger[0, 9]Numeric priority. 9 is critical and 0 is not an event.
statestringunacknowledgedacknowledgedprocessedThe state of the alarm. Alarms start at "unacknowledged".
activebooleanAlarms can be stateless or stateful. Stateless alarm types are spikes: an operator getting their password wrong or a cardholder being denied at a door. Stateful alarms have a triggering event and a restoring event: a door being forced open then closed, or a service going offline then reappearing later.
Stateless alarms are always inactive: active will be false. Stateful alarms will
have active true between being raised and restored.
An operator cannot process (dismiss) an active alarm without the 'Force process' privilege.
divisionobjectThe division entity representing the division of the alarm. GET the href for full details.
eventobjectA block containing a link ("href") to the details page of this alarm's event. Useful if you want an alarm's related items.
This mirrors an event's alarm field, which comes the other way.
Added in 8.90. Not returned by default - you need to ask for it using the fields query parameter.
notePresetsArray<string>Preset alarm notes to use for this specific alarm. Missing if the alarm does not have specific notes; in this case the client should fall back to the server defaults.
viewobjectPOST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.
commentobjectPOST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.
acknowledgeobjectPOST an alarm update request JSON object to the href to acknowledge an alarm. Missing if the system has mandatory alarm notes on for this alarm, or if the alarm is already acknowledged.
acknowledgeWithCommentobjectPOST an alarm update request JSON object to the href to acknowledge an alarm and place an alarm note against it. Missing if the alarm is already acknowledged.
processobjectPOST an alarm update request JSON object to the href to process an alarm. Missing if the system has mandatory alarm notes on for this alarm. Missing if the alarm cannot be processed (if it is active, for example).
processWithCommentobjectPOST an alarm update request JSON object to the href to process an alarm and place an alarm note against it. Missing if the alarm cannot be processed (if it is active, for example).
forceProcessobjectPOST an alarm update request JSON object to this href to process an active alarm. Missing
if the alarm is not active (in which case you will have process and processWithComment
links instead).
This action can take a comment in the body. In fact an explanation is recommended since force-processing an alarm is unusual.
{
"details": "Forced door",
"history": [
{
"time": "2016-02-18T19:21:52Z",
"action": "viewed",
"comment": "Operator viewed alarm properties",
"operator": {
"name": "System Operator"
}
}
],
"instruction": {
"href": "https://localhost:8904/api/alarms/92210/instructions",
"name": "Forced door instruction"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Smith, Jane",
"firstName": "Jane",
"lastName": "Smith-Jones"
},
"href": "https://localhost:8904/api/alarms/10135",
"id": "10135",
"time": "2016-02-18T19:21:52Z",
"message": "External bulk loading bay door has been forced",
"source": {
"id": "1321",
"name": "External bulk loading bay door",
"href": "https://localhost:8904/api/doors/1321"
},
"type": "Forced door",
"eventType": {},
"priority": 8,
"state": "unacknowledged",
"active": false,
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"event": {
"href": "https://localhost:8904/api/events/10135"
},
"notePresets": [
"False alarm confirmed by surveillance",
"Security staff dispatched"
],
"view": {
"href": "https://localhost:8904/api/alarms/92210/view"
},
"comment": {
"href": "https://localhost:8904/api/alarms/92210/comment"
},
"acknowledge": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"acknowledgeWithComment": {
"href": "https://localhost:8904/api/alarms/92210/acknowledge"
},
"process": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"processWithComment": {
"href": "https://localhost:8904/api/alarms/92210/process"
},
"forceProcess": {
"href": "https://localhost:8904/api/alarms/92210/process"
}
}AlarmHistoryEntry
objecttimestringThe time the history entry was added.
actionstringlegacycommentacknowledgeprocessacknowledgeActiveescalatedviewedThe type of the history entry.
commentstringThe added comment, or a textual description of some occurrence related to the alarm.
operatorobjectThe operator that created the history event.
{
"time": "2016-02-18T19:21:52Z",
"action": "viewed",
"comment": "Operator viewed alarm properties",
"operator": {
"name": "System Operator"
}
}AlarmZoneSummary
object/api/alarm_zones returns an array of these. It is a subset of what you get from a
alarm zone's detail page at /api/alarm_zones/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to an alarm zone detail object for this alarm zone. This is Command Centre's identifier for this alarm zone: use it whenever you need to specify an alarm zone in REST operations.
idstringAn alphanumeric identifier, unique to the server.
This is the ID to use in the source parameter of event
filters if you want to limit your events
to particular alarm zones.
namestring{
"href": "https://localhost:8904/api/alarm_zones/328",
"id": "328",
"name": "Roswell building 2 lobby alarms"
}AlarmZoneDetail
object/api/alarm_zones/{id} returns one of these.
/api/alarm_zones returns an array of these. It is a subset of what you get from a
alarm zone's detail page at /api/alarm_zones/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to an alarm zone detail object for this alarm zone. This is Command Centre's identifier for this alarm zone: use it whenever you need to specify an alarm zone in REST operations.
idstringAn alphanumeric identifier, unique to the server.
This is the ID to use in the source parameter of event
filters if you want to limit your events
to particular alarm zones.
namestringdescriptionstringdivisionobjectThe division containing this Alarm Zone.
shortNamestringnotesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
updatesobjectFollow the URL in the href inside this block to receive the item's current status,
then follow the next link in the results to long poll for changes to that status.
Update pages take the same fields parameter as summary and details pages. You
should use that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's
status is unknown until something is monitoring it. If you want status flags on the
search and details pages you must ask for them using the fields parameter, but our
advice is to monitor them using status
subscriptions if you are running 8.30 or later,
otherwise the item's updates link. See the item status
section for a full description of how to stay up to date with item status, and this
item's introduction in the Operations section for what flags this item might return
and what they mean.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
commandsobjectA block of commands, each represented by a block containing an href that accepts a POST that will send an override to the alarm zone, changing its state.
See the section 'Understanding Alarm Zones' in the Configuration client help for a description of alarm zone states.
It will be missing if your operator does not have a privilege that allows overriding the alarm zone (examples of which are in the documentation for the POSTs).
{
"href": "https://localhost:8904/api/alarm_zones/328",
"id": "328",
"name": "Roswell building 2 lobby alarms",
"description": "Lobby, cafeteria, inbound artefacts.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "R2 lobby",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/alarm_zones/328/updates/0_0_0"
},
"statusFlags": [
"armed"
],
"connectedController": {
"name": "Third floor C6000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"arm": {
"href": "https://localhost:8904/api/alarm_zones/328/arm",
"name": "Armed"
},
"armUntil": {
"href": "https://localhost:8904/api/alarm_zones/328/arm",
"name": "Armed"
},
"disarm": {
"href": "https://localhost:8904/api/alarm_zones/328/disarm",
"name": "Disarmed"
},
"disarmUntil": {
"href": "https://localhost:8904/api/alarm_zones/328/disarm",
"name": "Disarmed"
},
"user1": {
"href": "https://localhost:8904/api/alarm_zones/328/user1",
"name": "User1"
},
"user1Until": {
"href": "https://localhost:8904/api/alarm_zones/328/user1",
"name": "User1"
},
"user2": {
"href": "https://localhost:8904/api/alarm_zones/328/user2",
"name": "User2"
},
"user2Until": {
"href": "https://localhost:8904/api/alarm_zones/328/user2",
"name": "User2"
},
"cancel": {
"href": "https://localhost:8904/api/alarm_zones/328/cancel"
}
}
}DESFireEncodingRequest
objectcsn: Card serial number (UID).cadFiles: Gallagher Application Directory file contents as hex. Required if CAD files exist on card.reason: Reason for encoding, e.g. "New card issuance", "Lost card replacement". Optional.
csnHexStringrequiredcadFilesobjectOptional data read from CAD files, if present on the card.
reasonstring{
"csn": "0x0123456789ABCDEF",
"cadFiles": {},
"reason": "string"
}DESFireFile
objectidintegerIndex of the file within the application.
dataHexStringrequiredaccessRightsHexStringrequiredcommunicationModeHexString{
"id": 0,
"data": "0x0123456789ABCDEF",
"accessRights": "0x0123456789ABCDEF",
"communicationMode": "0x0123456789ABCDEF"
}DESFireKey
objectidintegerIndex of the key.
namestringA description of the key. Not used in encoding.
dataHexStringrequiredversionHexString{
"id": 0,
"name": "string",
"data": "0x0123456789ABCDEF",
"version": "0x0123456789ABCDEF"
}DESFireApplication
objectidHexStringApplication ID in hex with leading 0x.
namestringA description of the application. Not used in encoding.
keysArray<DESFireKey>Map of key number or name to key details.
keySettings1HexStringkeySettings2HexStringfilesArray<DESFireFile>requiredMap of file number to file content/access rights.
{
"id": "0x0123456789ABCDEF",
"name": "string",
"keys": [
{
"id": 0,
"name": "string",
"data": "0x0123456789ABCDEF",
"version": "0x0123456789ABCDEF"
}
],
"keySettings1": "0x0123456789ABCDEF",
"keySettings2": "0x0123456789ABCDEF",
"files": [
{
"id": 0,
"data": "0x0123456789ABCDEF",
"accessRights": "0x0123456789ABCDEF",
"communicationMode": "0x0123456789ABCDEF"
}
]
}CallbackLink
objecthrefstringrequiredAbsolute or relative URL for callback invocation.
{
"href": "string"
}CallbackLinks
objectencodeCompletedCallbackLinkrequiredencodeFailedCallbackLinkrequired{
"encodeCompleted": {
"href": "string"
},
"encodeFailed": {
"href": "string"
}
}EncryptedEncodingData
objectcipherTextstringrequiredThe encrypted encoding data, encoded as a base64 string.
ivstringrequiredThe initialization vector used for encryption, encoded as a base64 string.
ephemeralPublicKeystringrequiredThe ephemeral public key used for encryption, encoded as a base64 string.
tagstringrequiredThe authentication tag for the encrypted data, encoded as a base64 string.
{
"cipherText": "string",
"iv": "string",
"ephemeralPublicKey": "string",
"tag": "string"
}EncryptedEncodingDataResponseOrDecryptedForInfo
objectencryptedEncodingDataEncryptedEncodingDatarequiredcallbackCallbackLinksrequiredcardMasterKeyHexStringdefaultAppKeyHexStringapplicationsArray<object>sectorsArray<object>{
"encryptedEncodingData": {
"cipherText": "string",
"iv": "string",
"ephemeralPublicKey": "string",
"tag": "string"
},
"callback": {
"encodeCompleted": {
"href": "string"
},
"encodeFailed": {
"href": "string"
}
}
}EncryptedEncodingDataResponse
objectencryptedEncodingDataEncryptedEncodingDatarequiredcallbackCallbackLinksrequired{
"encryptedEncodingData": {
"cipherText": "string",
"iv": "string",
"ephemeralPublicKey": "string",
"tag": "string"
},
"callback": {
"encodeCompleted": {
"href": "string"
},
"encodeFailed": {
"href": "string"
}
}
}DecryptedDESFireDataForInfo
objectThis is not actual API data. It is for information only.
cardMasterKeyHexStringdefaultAppKeyHexStringapplicationsArray<object>{
"cardMasterKey": "0x0123456789ABCDEF",
"defaultAppKey": "0x0123456789ABCDEF",
"applications": [
{
"id": "string",
"name": "string",
"keys": [
{
"id": 0,
"name": "string",
"data": "0x0123456789ABCDEF",
"version": 1
}
],
"keySettings1": "string",
"keySettings2": "string",
"files": [
{
"id": 0,
"data": "0x0123456789ABCDEF",
"accessRights": "0x0123456789ABCDEF",
"communicationMode": "0x0123456789ABCDEF"
}
]
}
]
}DecryptedClassicDataForInfo
objectThis is not actual API data. It is for information only.
sectorsArray<object>{
"sectors": [
{
"id": 0,
"name": "string",
"blocks": [
{
"id": 0,
"file": "0x0123456789ABCDEF"
}
]
}
]
}DESFireEncodingResponse
objectapplicationsArray<DESFireApplication>requiredMap of application ID in hex with leading 0x to application details.
cardMasterKeystringThe value of the AES key that the cards's master key would be set to if encoded from Command Centre
defaultAppKeystringThe value that the card's default app key would be set to if encoded from Command Centre
{
"applications": [
{
"id": "0x0123456789ABCDEF",
"name": "string",
"keys": [
{
"id": 0,
"name": "string",
"data": "0x0123456789ABCDEF",
"version": "0x0123456789ABCDEF"
}
],
"keySettings1": "0x0123456789ABCDEF",
"keySettings2": "0x0123456789ABCDEF",
"files": [
{
"id": 0,
"data": "0x0123456789ABCDEF",
"accessRights": "0x0123456789ABCDEF",
"communicationMode": "0x0123456789ABCDEF"
}
]
}
],
"cardMasterKey": "string",
"defaultAppKey": "string"
}ClassicBlocks
objectMap of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.
{}ClassicCad
objectsectorintegerrequiredblocksClassicBlocksrequiredMap of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.
{
"sector": 0,
"blocks": {}
}ClassicMad
objectblocksClassicBlocksrequiredMap of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.
{
"blocks": {}
}ClassicEncodingRequest
objectcsn: Card UID.cad: Gallagher Application Directory sector/blocks (required if present on card).mad: MAD bytes (required if present on card).unavailableSectors: Sectors already occupied or reserved.reason: Reason for encoding, e.g. "New card issuance", "Lost card replacement". Optional.
csnHexStringrequiredcadClassicCadmadClassicMadunavailableSectorsArray<integer>reasonstring{
"csn": "0x0123456789ABCDEF",
"cad": {
"sector": 0,
"blocks": {}
},
"mad": {
"blocks": {}
},
"unavailableSectors": [
0
],
"reason": "string"
}ClassicSector
objectnamestringidintegerblocksArray<ClassicBlocks>requiredList of blocks in this sector to be encoded.
{
"name": "string",
"id": 0,
"blocks": [
{}
]
}ClassicEncodingResponse
objectsectorsArray<ClassicSector>requiredMap of sector number to sector data/blocks.
{
"sectors": [
{
"name": "string",
"id": 0,
"blocks": [
{}
]
}
]
}CardholderSearch
objectAn array of cardholder summaries, and a next link for more.
resultsArray<CardholderSummary>An array of cardholder summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://host.com:8904/api/cardholders/325",
"id": "325",
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true
}
],
"next": {
"href": "https://host.com:8904/api/cardholders?skip=61320"
}
}CardholderSummary
objectThe cardholder search at /api/cardholders returns an array of these. It is a subset of what
you get from a cardholder's detail page at /api/cardholders/{id} (linked as the href in this
object), to be more suitable for large result sets.
hrefstring<uri-reference>read onlyA link to a cardholder detail object for this cardholder. This is Command Centre's identifier for this cardholder: use it whenever you need to specify a cardholder in REST operations.
idstringread onlyAn alphanumeric identifier, unique to the server. No API calls use cardholder IDs.
Deprecated. Use the href instead.
firstNamestringlastNamestringshortNamestringIf you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it.
descriptionstringauthorisedbooleanThis is called 'Cardholder Authorised' in the administrative clients. If false, Command Centre will deny card access decisions for this cardholder.
authorised is false by default. If you want a new cardholder to open doors, be sure to
set it to true in your POST.
You need the 'Edit cardholders' privilege to set this true. In versions prior to 8.80 your operator also needed that privilege to set it false, but in 8.80 and later 'De-authorise cardholder' is enough on its own.
{
"href": "https://host.com:8904/api/cardholders/325",
"id": "325",
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true
}CardholderCommonFields
objectThe fields shared by the cardholder GET, POST, and PATCH.
disableCipherPadbooleandeprecatedTrue if this cardholder should not have the numbers on an alarms terminal scrambled when the terminal is configured as a cipher pad. Usually the reason is a vision impairment, making a randomised keypad impracticable.
Deprecated from 9.50 in favour of the disableCipherPad element in the accessibilities
array. This field can still be set for backwards compatibility, but only set one of the
two: setting them to disagreeing values will earn you a 400.
divisionobjectThe division containing this cardholder. You must send this when creating a cardholder.
notesstringFree-form text.
The 'Edit Cardholder Notes' privilege lets you change cardholder notes however you
like, but 'Add Cardholder Notes' only lets you add more to the end. To do that, read
the existing notes field, append your text, and send it back in a PATCH. If you try
to change the existing text the server will respond with a 4xx.
You will need one of those two privileges to edit notes. 'Create Cardholders' and 'Edit Cardholders' let you set most cardholder fields, but not notes.
Because of the potential size of the notes field, the server does not return it unless
you ask for it with fields=notes.
notificationsobjectThis block shows the cardholder's notification settings. It deserves some explanation.
The enabled bool is correct at the time of your call. If false, CC will not be
sending your cardholder any notifications.
The from and until date-times show if and when enabled will change. When the
from date-time passes, enabled will flip, the from date-time will take the
until value, and until will become null. This continues until from is null.
You can set from with a null until, but not until with a null from. They must
both be in the future or null, and from must be before until.
If you want to simply turn a person's notifications on or off without any changes
scheduled for the future, set enabled true or false and both from and until
null.
oidcLoginEnabledbooleanIf true, and they have an oidcUserId, the operator can log in to the Command Centre
client using their Entra ID username.
Added in 9.40.
oidcUserIdstringThis user's Entra ID username. This should be the operator's Entra ID 'User Principal Name' found in the overview of the user's details in Entra ID Admin Center.
Send the empty string "" to clear it.
Added in 9.40.
operatorLoginEnabledbooleanTrue if this operator can log in to the interactive Command Centre clients. Note that a cardholder also needs a privilege to use the Configuration Client.
operatorUsernamestringThe name that the operator uses when logging in to the interactive clients. Absent if blank.
Send the empty string "" to clear it.
operatorPasswordExpiredbooleanIf true, the interactive clients will request a new password the next time this person logs in.
personalDataDefinitionsArray<CardholderPDF>All the personal data definitions and values for this cardholder. It is an array of
objects, each with one key/value pair. The key is the name of the PDF preceded by
an '@'; the value is an object containing the definition of the PDF (common for all
cardholders) and the value for this cardholder. The value block of an image PDF will
contain an href to the image.
Your visibility of a cardholder's PDF value depends entirely on the PDF's access settings in your operator groups, or if it has none of those, its default privilege. The PDF's division is irrelevant. So if you were expecting a PDF value here when none arrived, ensure the cardholder has a value for that PDF (because the server does not send nulls) then check the PDF's access settings in all of your operator groups, then the default visibility on the PDF item itself.
When you send this array in a POST or PATCH, Command Centre will take the
notifications flag from here. It can take value from here too, but if you also
send a field in the root of the cardholder with the name of the PDF preceded by an
'@', that one wins.
To request this block using the fields parameter, use personalDataFields (note
'fields' not 'definitions'). Doing so will not only give you the
personalDataDefinitions block, but also the values at the root level (with their names
preceded by '@'-signs).
windowsLoginEnabledbooleanIf true, and they have a Windows username, this user can log in using Windows integrated authentication.
windowsUsernamestringThis user's Windows username. For an example of the format that will work in your organisation, look in the 'Windows logon' field of the 'Operator configuration' tab of a cardholder's properties in the Configuration Client.
Send the empty string "" to clear it.
{
"disableCipherPad": false,
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00.000Z",
"until": "2017-10-17T14:59:00.000Z"
},
"oidcLoginEnabled": true,
"oidcUserId": "admin_qmaster@misix.gov.uk",
"operatorLoginEnabled": true,
"operatorUsername": "qmaster",
"operatorPasswordExpired": false,
"personalDataDefinitions": [
{
"@Student ID": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2356",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/2356",
"name": "Student ID",
"id": "2356",
"type": "string"
},
"value": "8904640"
}
},
{
"@Photo": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2369",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/2369",
"name": "Photo",
"id": "2369",
"type": "image"
},
"value": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2369"
}
}
}
],
"windowsLoginEnabled": true,
"windowsUsername": "misix.local\\qmaster"
}CardholderDetail
object/api/cardholders/{id} returns one of these, and you submit parts of one in a POST to create a cardholder.
You may find it contains a block called updates. This is reserved for future
development and its behaviour will change in later versions of Command Centre.
The cardholder search at /api/cardholders returns an array of these. It is a subset of what
you get from a cardholder's detail page at /api/cardholders/{id} (linked as the href in this
object), to be more suitable for large result sets.
hrefstring<uri-reference>read onlyA link to a cardholder detail object for this cardholder. This is Command Centre's identifier for this cardholder: use it whenever you need to specify a cardholder in REST operations.
idstringread onlyAn alphanumeric identifier, unique to the server. No API calls use cardholder IDs.
Deprecated. Use the href instead.
firstNamestringlastNamestringshortNamestringIf you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it.
descriptionstringauthorisedbooleanThis is called 'Cardholder Authorised' in the administrative clients. If false, Command Centre will deny card access decisions for this cardholder.
authorised is false by default. If you want a new cardholder to open doors, be sure to
set it to true in your POST.
You need the 'Edit cardholders' privilege to set this true. In versions prior to 8.80 your operator also needed that privilege to set it false, but in 8.80 and later 'De-authorise cardholder' is enough on its own.
The fields shared by the cardholder GET, POST, and PATCH.
disableCipherPadbooleandeprecatedTrue if this cardholder should not have the numbers on an alarms terminal scrambled when the terminal is configured as a cipher pad. Usually the reason is a vision impairment, making a randomised keypad impracticable.
Deprecated from 9.50 in favour of the disableCipherPad element in the accessibilities
array. This field can still be set for backwards compatibility, but only set one of the
two: setting them to disagreeing values will earn you a 400.
divisionobjectThe division containing this cardholder. You must send this when creating a cardholder.
notesstringFree-form text.
The 'Edit Cardholder Notes' privilege lets you change cardholder notes however you
like, but 'Add Cardholder Notes' only lets you add more to the end. To do that, read
the existing notes field, append your text, and send it back in a PATCH. If you try
to change the existing text the server will respond with a 4xx.
You will need one of those two privileges to edit notes. 'Create Cardholders' and 'Edit Cardholders' let you set most cardholder fields, but not notes.
Because of the potential size of the notes field, the server does not return it unless
you ask for it with fields=notes.
notificationsobjectThis block shows the cardholder's notification settings. It deserves some explanation.
The enabled bool is correct at the time of your call. If false, CC will not be
sending your cardholder any notifications.
The from and until date-times show if and when enabled will change. When the
from date-time passes, enabled will flip, the from date-time will take the
until value, and until will become null. This continues until from is null.
You can set from with a null until, but not until with a null from. They must
both be in the future or null, and from must be before until.
If you want to simply turn a person's notifications on or off without any changes
scheduled for the future, set enabled true or false and both from and until
null.
oidcLoginEnabledbooleanIf true, and they have an oidcUserId, the operator can log in to the Command Centre
client using their Entra ID username.
Added in 9.40.
oidcUserIdstringThis user's Entra ID username. This should be the operator's Entra ID 'User Principal Name' found in the overview of the user's details in Entra ID Admin Center.
Send the empty string "" to clear it.
Added in 9.40.
operatorLoginEnabledbooleanTrue if this operator can log in to the interactive Command Centre clients. Note that a cardholder also needs a privilege to use the Configuration Client.
operatorUsernamestringThe name that the operator uses when logging in to the interactive clients. Absent if blank.
Send the empty string "" to clear it.
operatorPasswordExpiredbooleanIf true, the interactive clients will request a new password the next time this person logs in.
personalDataDefinitionsArray<CardholderPDF>All the personal data definitions and values for this cardholder. It is an array of
objects, each with one key/value pair. The key is the name of the PDF preceded by
an '@'; the value is an object containing the definition of the PDF (common for all
cardholders) and the value for this cardholder. The value block of an image PDF will
contain an href to the image.
Your visibility of a cardholder's PDF value depends entirely on the PDF's access settings in your operator groups, or if it has none of those, its default privilege. The PDF's division is irrelevant. So if you were expecting a PDF value here when none arrived, ensure the cardholder has a value for that PDF (because the server does not send nulls) then check the PDF's access settings in all of your operator groups, then the default visibility on the PDF item itself.
When you send this array in a POST or PATCH, Command Centre will take the
notifications flag from here. It can take value from here too, but if you also
send a field in the root of the cardholder with the name of the PDF preceded by an
'@', that one wins.
To request this block using the fields parameter, use personalDataFields (note
'fields' not 'definitions'). Doing so will not only give you the
personalDataDefinitions block, but also the values at the root level (with their names
preceded by '@'-signs).
windowsLoginEnabledbooleanIf true, and they have a Windows username, this user can log in using Windows integrated authentication.
windowsUsernamestringThis user's Windows username. For an example of the format that will work in your organisation, look in the 'Windows logon' field of the 'Operator configuration' tab of a cardholder's properties in the Configuration Client.
Send the empty string "" to clear it.
lastSuccessfulAccessTimestring<date-time>read onlyThe date and time of the last successful card event or operator-initiated movement (using a tag board or this API).
lastSuccessfulAccessZoneobjectread onlyThe last zone the cardholder entered.
If their last movement was to the 'outside' (through a door that has no access zone
configured for that direction of travel) this field will be missing but
lastSuccessfulAccessTime will be present.
If the cardholder is a visitor and their last movement was to leave the site, this will be their visit's reception, not an access zone. 8.50 and later will present only the name of the reception and withhold the href in that case.
Change is coming.
A future version of Command Centre will add the href field even if the item is a
reception. So you can tell the difference, it will add a field called
canonicalTypeName which will have one of two values: accesszone or reception.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
usercodestringThis field is write-only, so you will not see it in the results of a GET. It appears here because you can send it in the body of a POST or PATCH.
Set it to the empty string "" to clear the cardholder's user code.
If it is not empty it must be a string of at least four digits. It may need to be longer, because the minimum length is set by your site configuration.
You will find that "0000" does not work.
operatorPasswordstringThe password this user must supply when logging in to the interactive clients, if they are not using Windows integrated authentication.
Command Centre imposes password length and complexity restrictions. If your password does not meet those requirements, the entire update will fail. The body of the 4xx response will tell you why.
Like the user code, this is write-only so you will not see it in the results of a GET.
cardsArray<CardholderCard>All the cards for this cardholder. Note that even though Command Centre calls these things 'cards', they include other types that do not require a physical card, such as digital IDs and mobile credentials.
accessGroupsArray<CardholderAccessGroup>All the cardholder's access group memberships. This does not list memberships the cardholder inherits through other groups.
The server property 'Show all cardholder access group memberships' does not affect the API as it affects the configuration and operational clients, so this field only appears if the operator has permission to see access group memberships. That is granted by a privilege such as 'View cardholders'.
operatorGroupsArray<CardholderOperatorGroup>All the cardholder's operator group memberships. Added in 8.50.
competenciesArray<CardholderCompetency>Every competency a cardholder possesses. There is quite a lot there: see the schema definition for detailed explanation.
editobjectread onlyReserved for internal use. It is a link to another call that helps interactive administrative clients. Do not send it when creating or editing a cardholder.
updateLocationobjectread onlyLink to POST to when you want to update the location of this cardholder. Added in 8.20.
relationshipsArray<CardholderRelationship>This is a list of the roles that other cardholders perform for this cardholder.
Since only one cardholder can perform a given role for another, there will be no more elements in this array than there are roles on the site.
The example shows that this cardholder has one supervisor.
lockersArray<CardholderLocker>All the cardholder's locker assignments.
elevatorGroupsArray<CardholderElevatorGroup>The cardholder's elevator group properties. They may have one per elevator group.
Added in 8.50.
updatesobjectCardholders include an updates block, as other items do, that allows monitoring one
cardholder per TCP session in a long poll. However the Cardholder
changes methods are a far more efficient way of monitoring
your cardholders.
redactionsArray<CardholderRedaction>All the cardholder's scheduled redactions.
Because this is relatively expensive to compute, this field does not appear by
default. You must ask for it with fields=redactions or
fields=redactions.href,redactions.type,redactions.when, for example.
Added in 8.80.
accessibilitiesArray<object>The cardholder's accessibility settings in an array of three elements.
New to 9.50.
{
"href": "https://host.com:8904/api/cardholders/325",
"id": "325",
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"disableCipherPad": false,
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00.000Z",
"until": "2017-10-17T14:59:00.000Z"
},
"oidcLoginEnabled": true,
"oidcUserId": "admin_qmaster@misix.gov.uk",
"operatorLoginEnabled": true,
"operatorUsername": "qmaster",
"operatorPasswordExpired": false,
"personalDataDefinitions": [
{
"@Student ID": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2356",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/2356",
"name": "Student ID",
"id": "2356",
"type": "string"
},
"value": "8904640"
}
},
{
"@Photo": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2369",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/2369",
"name": "Photo",
"id": "2369",
"type": "image"
},
"value": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/2369"
}
}
}
],
"windowsLoginEnabled": true,
"windowsUsername": "misix.local\\qmaster",
"lastSuccessfulAccessTime": "2004-11-18T19:21:52.000Z",
"lastSuccessfulAccessZone": {
"href": "https://host.com:8904/api/access_zones/333",
"name": "Twilight zone"
},
"serverDisplayName": "ruatoria.satellite.int",
"usercode": "numeric, and write-only",
"operatorPassword": "write-only",
"cards": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9",
"number": "1",
"cardSerialNumber": "045A5769713E80",
"issueLevel": 1,
"status": {
"value": "Disabled (manually)",
"type": "inactive"
},
"type": {
"href": "https://host.com:8904/api/card_types/354",
"name": "Card type no. 1"
},
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789",
"singleFactorOnly": false,
"status": "sent",
"href": "https://security.gallagher.cloud/api/invitations/abcd1234defg5678"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z",
"credentialClass": "mobile",
"trace": false,
"lastPrintedOrEncodedTime": "2020-08-10T09:20:50.000Z",
"lastPrintedOrEncodedIssueLevel": 1,
"lastUsedTime": "2025-09-24T14:00:00.000Z",
"pin": "153624",
"visitorContractor": false,
"ownedBySite": false,
"credentialId": "reserved",
"bleFacilityId": "reserved",
"encodingData": {
"mifareClassic": {
"href": "string"
},
"mifareDesfire": {
"href": "string"
}
}
}
],
"accessGroups": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/D714D8A89F",
"accessGroup": {
"name": "R&D special projects group",
"href": "https://host.com:8904/api/access_groups/352"
},
"status": {
"value": "Pending",
"type": "pending"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z"
}
],
"operatorGroups": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD",
"operatorGroup": {
"name": "Locker admins",
"href": "https://host.com:8904/api/operator_groups/532"
}
}
],
"competencies": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3p0",
"competency": {
"href": "https://host.com:8904/api/competencies/2354",
"name": "Hazardous goods handling"
},
"status": {
"value": "Pending",
"type": "pending"
},
"disabled": true,
"enabled": true,
"expiryWarning": "2017-03-06T15:45:00.000Z",
"expiry": "2017-03-09T15:45:00.000Z",
"enablement": "2018-03-09T15:45:00.000Z",
"comment": "CPR refresher due March.",
"limitedCredit": true,
"credit": 37
}
],
"edit": {
"href": "https://host.com:8904/cardholders/325/edit"
},
"updateLocation": {
"href": "https://host.com:8904/api/cardholders/402/update_location"
},
"relationships": [
{
"href": "https://host.com:8904/api/cardholders/325/relationships/179lah1170",
"role": {
"href": "https://host.com:8904/api/roles/5396",
"name": "Supervisor"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398",
"name": "Miles Messervy",
"firstName": "Miles",
"lastName": "Messervy"
}
}
],
"lockers": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/t1m4",
"locker": {
"name": "Bank A locker 1",
"shortName": "A1",
"lockerBank": {
"href": "https://host.com:8904/api/locker_banks/4567",
"name": "Bank A"
},
"href": "https://host.com:8904/api/lockers/3456"
},
"from": "2017-01-01T00:00:00Z",
"until": "2018-12-31T00:00:00Z"
}
],
"elevatorGroups": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/567",
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635",
"name": "Main building lower floors"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637",
"name": "Lvl 1 lift lobby"
},
"enableCaptureFeatures": true,
"enableCodeBlueFeatures": false,
"enableExpressFeatures": true,
"enableServiceFeatures": false,
"enableService2Features": true,
"enableService3Features": true,
"enableVipFeatures": false
}
],
"updates": {},
"redactions": [
{
"href": "https://localhost:8904/api/cardholders/redactions/625",
"type": "normalEvents",
"when": "2023-01-01T00:00:00Z",
"before": "2022-01-01T00:00:00Z",
"status": "pending",
"redactionOperator": {
"name": "REST Operator",
"href": "https://localhost:8904/api/items/100"
}
}
],
"accessibilities": [
{
"type": "useExtendedAccessTime",
"enabled": true,
"from": "2026-08-15T21:00:00Z"
},
{
"type": "disableCipherPad",
"enabled": false
},
{
"type": "pinExemption",
"enabled": false
}
]
}CardholderPDF
objectA personal data definition and its value for a cardholder. Each definition is an object containing one property named after the PDF (plus a leading '@'), which in turn contains its value and notifications flag for the containing cardholder and an object containing some of the PDF definition's basic fields.
This is returned as part of a cardholder, and you can send it
in a POST or a
PATCH to set the PDF's value and notification flag for
a cardholder. The server will ignore the definition block if you send it back. It uses the
name of the block (minus its leading @) to find the PDF to change.
While you can use this structure to change the value of a cardholder's PDF, it may be simpler to put a property in the root of the payload named after the PDF with a leading '@'.
{
"@cellphone": {
"href": "https://host.com:8904/api/cardholders/325/personal_data/9997",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/9997",
"name": "cellphone",
"id": "9997",
"type": "mobile"
},
"value": "a@b.com",
"notifications": false
}
}CardholderPDFContent
objecthrefstring<uri-reference>read onlyGET this link to receive this cardholder's value for this PDF. If it is an image you will receive the raw image data with an appropriate content-type. A browser will render it correctly.
This field will be absent if the cardholder does not have a value for the PDF.
It is read-only: do not send it in a PATCH or POST.
If the PDF is not an image the content type will be text/plain, encoded (like everything else in this API) as UTF-8. But you should only use it for image PDFs because:
Values for scalar PDF types (everything except images) are also given to you in the
valuefield in this object, described below.The format of date PDFs varies with the Command Centre version. Old servers return it in a format that depends on the server's locale. This was a bug. Newer servers use ISO 8601.
If you learn this URL while a cardholder has the PDF, then the cardholder loses it, then you GET the URL, results will vary depending on the server version. The correct result is a 404, and if the PDF is an image or the server is recent you will see that. But old servers may return a blank string or even a 500 for non-image PDFs.
definitionobjectread onlyThe definition object is read-only: do not send it in a PATCH or POST.
valuestring | objectIn a GET response for text, numeric, and date PDFs this will be a scalar containing the
value, but for image PDFs it will be an object containing a copy of the href field
above. For date PDFs the string will comply to ISO 8601, but may or may not contain a
time depending on the version of Command Centre. Newer versions omit it. If there,
it will be midnight UTC.
In versions up to and including 8.60, if a cardholder did not have an image PDF captured
(in other words they were a member of an access group that included an image PDF, but they
did not have a value for it) the API returned the string 'Not captured' in the value
field. Versions 8.70 and later will not return the value field at all if there is no image.
In a POST or PATCH send text PDFs (including dates) as strings, numeric PDFs as numbers or strings that parse to numbers, and image data Base64-encoded into a string.
notificationsbooleanIn a GET response, this will only appear for email and mobile PDF types. In a POST or PATCH, it only makes sense for the same types.
If true, cardholder notifications will go to the telephone number or email address held by this PDF. Cardholders can have their notifications go to as many contacts as they wish.
{
"href": "https://host.com:8904/api/cardholders/325/personal_data/9998",
"definition": {
"href": "https://host.com:8904/api/personal_data_fields/9998",
"name": "cellphone",
"id": "9998",
"type": "mobile"
},
"value": "a@b.com",
"notifications": false
}CardholderCard
objectA card is an access credential, including physical cards and mobile (Bluetooth and NFC) credentials and digital IDs. The server returns it as part of a cardholder's details, and you supply it when creating or modifying a card or credential on a cardholder.
Every card has a type, a status, and a validity period. Its type determines other fields of relevance, described below.
PIV cards are complex enough to warrant a tag of their own.
hrefstring<uri-reference>read onlyDELETE this link to delete a card.
Do not specify it when creating a card.
DELETE is the only verb you can use on this URL. GET will always return a 404 in the current versions of Command Centre.
numberstringFor a physical access card, this is its card number. It must be unique across all cards of the same card type. If you leave it blank when creating a card of a type that has a decimal number format, Command Centre will use the next available number.
Card numbers for a mobile credential need not be unique. They are strings, and are not used by Command Centre except for display.
Card numbers for digital IDs are GUIDs.
This field is mandatory for card types with text or PIV number formats. If the card type has a text card number format with a regular expression and you supply a card number that does not match that regex, Command Centre will reject your update.
PIV card numbers must be the same as the card's FASC-N. PIV-I card numbers must not.
One rule is common across all card types: you cannot change the number of an existing card or credential.
cardSerialNumberstringThe serial number (CSN, MIFARE UID) of a physical access card as a hex string without a leading '0x'. The number is encoded in the traditional way, with an even number of numbers and the letters from 'A' through 'F', but the '0x' is missing when it comes out of the API and it must be missing when you send it back.
The example in this example shows a seven-byte serial in the order you can expect in a GET and should use in a PATCH or POST. Those on NXP MIFARE cards start with 04 and end with 80 or 90.
Four-byte serial numbers are in the same format, but shorter: "EBD62D25", for example.
This may not be not present on very old cards.
This field was read-only until 7.90. It is read-write in 8.00 and later.
issueLevelinteger[0, 15]The issue level of a physical access card. If two cards have the same number but different issue levels, only the one with this issue level will gain access.
If you leave it blank when creating a card, Command Centre will pick an appropriate value.
If you increase it when modifying a card, all cards with lower issue levels will stop working.
Do not specify one when creating or updating a mobile credential or digital ID. They do not have issue levels.
statusobjectEach card has two status codes: value, and type, covered in separate sections below.
value is human-readable. It comes from the Card State Set configured by the site, and
could be adjusted according to the card's activation dates. The REST API and this
document do not cover card state sets because in most cases, the default set is
sufficient. See the online help for the Command Centre client if you wish to create your
own.
The second field, type, comes from a fixed enumeration, and so is better suited than
value for integrations. Command Centre derives it from the card's state and activation
dates.
typeobjectThe name of the card type from the site configuration, and a link to the card type object.
When creating a card, the href must be a card type that makes sense for the other values you placed this object.
You cannot change a card's type once it is created.
invitationobjectCommand Centre will only return this object for a mobile credential, and you should send it only when creating one. In the interests of security you cannot modify the invitation block of an existing credential. Someone's thumb might be on its way to accept it, after all. If there was something wrong with it you should delete it and start afresh.
Whether you should send mobile or email when creating a mobile credential depends on
whether you are using the Gallagher mobile apps or your own.
If you specify either mobile or mail to early-version servers you must also supply the
other so that Command Centre can send both an email invitation and a confirmation SMS. It
is an error to only specify one on those servers.
Later versions of Command Centre made the SMS verification optional to better support installations that use the Gallagher apps but prefer not to depend on cellular connectivity. You do not need to send a mobile number when creating such a credential.
If you do not give an email address Command Centre cannot send an invitation to the cardholder. It will be up to another application to complete the creation of this credential using the Mobile Connect SDK.
You should not send a mobile number when creating a credential for use by third-party apps that use the Mobile Connect SDK. SMS is not supported there.
For more detail on using the Mobile Connect SDK, including more complete coverage of how an email address and mobile number are used in the provisioning process, see its documentation at gallaghersecurity.github.io.
fromstring<date-time>The start of the time period during which this card is active. If this time is in the future, the card is not active.
When it is not set, Command Centre acts as though it is set to a time in the distant past.
When modifying a card, send a null string "" to reset it.
It must be before midnight on the morning of January 1, 2100 local time (so servers with
positive timezone offsets have maximums during December 31 2099), and it must be less than
or equal to than the until time. A future version of Command Centre will clamp too-high
values to December 31 2099 and return a 2xx (success) instead of 400 in that case.
The server will reject timestamps in card updates that are not in an acceptable format. But in requests to create a card, rather than modify an existing card, the server will ignore such strings and use the card type's defaults. This is undesirable behaviour and will change in a future version of Command Centre.
untilstring<date-time>The end of the time period during which this card is active. If this time is in the past, the card is not active.
When it is not set, Command Centre acts as though it is set to a time in the distant future.
When modifying a card, send a null string "" to reset it.
It must be before midnight on the morning of January 1, 2100 local time (so servers with
positive timezone offsets have maximums during December 31 2099), and it must be greater
than or equal to the from time. A future version of Command Centre will clamp too-high
values to December 31 2099 and return a 2xx (success) instead of 400 in that case.
The server will reject timestamps in card updates that are not in an acceptable format. But in requests to create a card, rather than modify an existing card, the server will ignore such strings and use the card type's defaults. This is undesirable behaviour and will change in a future version of Command Centre.
credentialClassstringcarddigitalIdgovPassmobilepivpivitrackingTagtransactread onlyThis indicates the type of the card. It comes from an enumeration, and is a reliable way of determining the credential's type.
Added in 8.00.
The experimental applePass was added in 9.10. Its name may change in future versions.
The govPass credential class will change its name in 9.30.
tracebooleanIf set, using this credential will generate an event.
This field is not in the default set so you will not see it unless you ask for it using
the fields query parameter.
Added in 8.30.
lastPrintedOrEncodedTimestring<date-time>read onlyThe date and time this card was last printed or encoded. It will not come out by
default - you need to ask for it with fields=cards.lastPrintedOrEncodedTime.
Added in 8.40.
lastPrintedOrEncodedIssueLevelinteger[1, 15]read onlyThe issue level of this card when it was last printed or encoded, provided it was
non-zero. It will not come out by default - you need to ask for it with
fields=cards.lastPrintedOrEncodedIssueLevel.
Added in 8.40.
lastUsedTimestring<date-time>read onlyWhen a Gallagher controller last observed the credential in use.
pinstringThis is a write-only field. You can use it in POSTs and PATCHes but the server will not send it to you.
Being numeric, you might be tempted to send PINs to the server without surrounding quotes. However if you do that, leading zeros will be lost and the resulting behaviour is undefined.
8.90 servers will reject your request if the card's credentialClass is not card,
piv, piv-i, or govPass. More recent servers will issue a warning in that case, and
will not set the PIN, but will allow the rest of the update.
PINs were added to the API in 8.90.
visitorContractorbooleanThis is a credential property only for GovPass, indicating that the enrolled credential is for a visitor / contractor.
It is a write-once field new to 9.00.
ownedBySitebooleanread onlyThis is a credential property only for GovPass, indicating if the site is the owner of the card.
It is a read-only field new to 9.00.
credentialIdstringReserved for use by Gallagher applications.
bleFacilityIdstringReserved for use by Gallagher applications.
encodingDataobjectread onlyThis block contains links to the calls that return the data you will need to encode MIFARE DESFire and Classic cards. For more on that see the 'Card Encoding' tag.
Read-only: the server will ignore it if you send it.
Added in 9.50.
Not returned by default: ask for it by adding cards.encodingData to your fields
query parameter.
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9",
"number": "1",
"cardSerialNumber": "045A5769713E80",
"issueLevel": 1,
"status": {
"value": "Disabled (manually)",
"type": "inactive"
},
"type": {
"href": "https://host.com:8904/api/card_types/354",
"name": "Card type no. 1"
},
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789",
"singleFactorOnly": false,
"status": "sent",
"href": "https://security.gallagher.cloud/api/invitations/abcd1234defg5678"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z",
"credentialClass": "mobile",
"trace": false,
"lastPrintedOrEncodedTime": "2020-08-10T09:20:50.000Z",
"lastPrintedOrEncodedIssueLevel": 1,
"lastUsedTime": "2025-09-24T14:00:00.000Z",
"pin": "153624",
"visitorContractor": false,
"ownedBySite": false,
"credentialId": "reserved",
"bleFacilityId": "reserved",
"encodingData": {
"mifareClassic": {
"href": "string"
},
"mifareDesfire": {
"href": "string"
}
}
}CardholderAccessGroup
objectAn access group is an object in Command Centre. This section is about a connection between an access group and a cardholder, called a membership. A cardholder can be a member of many groups, and groups can have any number of members.
Less obvious is that a cardholder can have many memberships to the same group. This is useful
because a membership has a validity period, expressed with from and until date-times.
Outside those moments Command Centre does not regard the cardholder as being a member of the
group. If there exists one membership with from in the past or unset and until in the
future or unset, the cardholder is a member.
Presence in an access group affects physical access rights and possession of PDFs, among other things.
The membership object can come from the server in a cardholder's details, and you send it to the server when modifying a cardholder's group memberships. All uses are described below.
hrefstring<uri-reference>DELETE this URL to remove this group membership, and use it in the body of a PATCH to a cardholder to identify memberships you want to modify.
Note that changing an access group membership with a PATCH will change this href. Do not cache it.
DELETE is the only verb you can use on this URL. GET returns a 404.
accessGroupobjectAn object containing a link to this group's detail page and (when sent by the the server) its name. You should not send it when modifying a group membership because you cannot change a group membership's group; you can only change its dates. But you must send it when adding a new group membership, of course, because it identifies the cardholder's new group. Just send the href: don't bother with the name.
8.70 and later will not return the link if your operator does not have the privilege to view the access group (given by 'View access groups', for example).
statusobjectread onlyThe two fields in this block are read-only because they are determined by the from and
until dates.
fromstring<date-time>The start of the time period during which this group membership is active. If this time is in the future, the card will be inactive.
When sending this to a server of version 7.90.883 or earlier, use UTC with a trailing 'Z'. More recent versions understand timezone offsets.
untilstring<date-time>The end of the time period during which this group membership is active. If this time is in the past, the card will be inactive.
When sending this to a server of version 7.90.883 or earlier, use UTC with a trailing 'Z'. More recent versions understand timezone offsets.
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/D714D8A89F",
"accessGroup": {
"name": "R&D special projects group",
"href": "https://host.com:8904/api/access_groups/352"
},
"status": {
"value": "Pending",
"type": "pending"
},
"from": "2017-01-01T00:00:00.000Z",
"until": "2017-12-31T11:59:59.000Z"
}CardholderOperatorGroup
objectAn operator group is an object in Command Centre. The connection between an operator group and a cardholder is a membership. A cardholder can be a member of many different operator groups, and operator groups usually have more than one member, but a cardholder can only have one membership to a given operator group at a time. This is because an operator group membership, unlike an access group membership, does not have start and end dates.
Presence in an operator group affects software access. Your REST operator, for example, must be in an operator group that grants privileges otherwise it will receive nothing but 404s.
Added to the API in 8.50.
hrefstring<uri-reference>DELETE this URL or use it in the operatorGroups block of a cardholder
PATCH to remove this operator group
membership.
DELETE is the only verb you can use on this URL. GET will always return a 404.
operatorGroupobjectAn object containing a link to this operator group's detail page and its name.
The link will be absent from the results of a GET if your operator does not have the privilege to view the operator group ('View operators' or 'Edit operators', for example).
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD",
"operatorGroup": {
"name": "Locker admins",
"href": "https://host.com:8904/api/operator_groups/532"
}
}CardholderCompetency
objectA competency is an object in Command Centre with some basic fields like a name and a notice period. Its purpose is to allow a site to refuse access to cardholders who do not meet a special requirement.
This section describes the link between a cardholder and a competency. The status of that link determines whether they "hold" the competency for the purposes of access control decisions at the door. When we talk about updating, adding, or deleting a competency in the cardholder API, we do not mean the competency object itself, but rather the link a cardholder has to the competency.
The link between cardholder and competency is a block in the cardholder detail. You can
also request the server to add it to the results of a cardholder search by putting
competencies in the fields parameter.
When updating a cardholder's holding of a competency, the fields you send are slightly
different from what you receive from a cardholder GET: you do not send read-only blocks
such status and competency, and there is a write-only field enabled that you can
supply to change the status.
You cannot use a PATCH to move the status, times, comment, and credit to a different competency. If you need to do that, delete the cardholder's existing competency and give them a new one. You can do that in one PATCH.
A cardholder can have only one link to a given competency. Attempting to give a cardholder a competency a second time will either fail or succeed and raise a stateful alarm, depending on your version of Command Centre.
hrefstring<uri-reference>read onlyThis is the URL of the cardholder's connection to a competency, not the URL of the competency itself. Use it as the target of a DELETE to remove a cardholder's competency, or in the body of a cardholder PATCH to modify it.
DELETE is the only verb you can use on this URL. GET will return a 404.
competencyobjectThis contains the competency's name and its href.
As ever, do not bother putting name in the body of a POST or PATCH. The server will
ignore it.
statusobjectread onlyThis object contains two strings. Both are read-only, so do not specify them when
assigning or updating a cardholder's competency. value is taken from the site's
language pack, suitable for display. type comes from a fixed enumeration. It will be
expiryDue or active when the cardholder carries this competency; anything else means
no. A fuller explanation follows.
A competency can be disabled, expired, both, or neither. Whether it is enabled is a flag on the cardholder's holding of the competency. Whether it is expired is derived from an expiry timestamp (accurate to the second): if it is in the past, Command Centre considers the competency expired.
A competency can also have an enablement date. If that date (timestamp) passes while the
competency is disabled, Command Centre will enable the competency. It will leave the date on
the item for future reference, though it will not affect the competency again.
If the competency is disabled, the status type will be inactive when there is no
enable date or it is in the past, and pending when the enable date is in the future
(i.e., there is an automatic re-enablement coming).
If the competency is not disabled, the expiry time is important. If it is in the past,
type will be expired.
All of those cases are negative. Two remain, when our cardholder is blessed with an
enabled and active competency. type will be expiryDue if the expires time is in the
future but within the competency's advance notice period, or active if the expires
time is beyond the advance notice period or not set at all.
You can see and set the enabled flag, the enable date, and the expiry date via this API.
When creating a cardholder or updating a competency on an existing cardholder, you should
set the enabled field one way or the other, and the enablement and expiry dates if
you wish. They will determine the contents of this status block.
Here it is in table form. The first two rows are the positive cases, when Command Centre would grant access to a competency-enforced zone.
| Enabled flag | Enablement date | Expiry date | status.type |
|---|---|---|---|
| true | - | Far future | active |
| true | - | Near future | expiryDue |
| true | - | Past | expired |
| false | Future | - | pending |
| false | Past | - | inactive |
| false | Unset | - | inactive |
disabledbooleanread onlydeprecatedDo not use this field. Instead, take the state of a cardholder's competency from
status.type. This field is misleading on its own: even when it is true, the
competency might still be inactive.
enabledbooleanThis is a write-only field. You will not receive it from the GET. It changes the
competency between enabled (status type expired, expiryDue, or active) and
disabled (status type inactive or pending). Set it to false in a PATCH if you want
to disable the competency now, particularly if you are setting a future enablement date.
expiryWarningstring<date-time>read onlyThe time at which the cardholder will (or did) receive a warning about the
competency expiring. If this time is set and in the past but the competency has
not yet expired and is still enabled, the status type will be expiryDue.
expirystring<date-time>The time at which the competency will expire. If this time is set, in the past, and the
competency is enabled, status type will be expired.
enablementstring<date-time>The time at which the competency will be re-enabled. If set and in the future, and the
competency is disabled, the status type will be pending.
You can set this on an enabled competency but it has no effect there, so with this field
you would generally also set enabled: false.
commentstringThe comment appears in the management clients when viewing the cardholder.
limitedCreditbooleanIf false, Command Centre's 'Pre-pay Car Parking' feature (available under its own licence) will not reduce the current credit.
This field will be in the results if its value is true.
creditintegerThe balance, or amount of credit left on this competency for use by Pre-pay Car Parking. It can be negative.
This field will be in the results if its value is not zero.
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3p0",
"competency": {
"href": "https://host.com:8904/api/competencies/2354",
"name": "Hazardous goods handling"
},
"status": {
"value": "Pending",
"type": "pending"
},
"disabled": true,
"enabled": true,
"expiryWarning": "2017-03-06T15:45:00.000Z",
"expiry": "2017-03-09T15:45:00.000Z",
"enablement": "2018-03-09T15:45:00.000Z",
"comment": "CPR refresher due March.",
"limitedCredit": true,
"credit": 37
}CardholderRelationship
objectA role is an object in Command Centre. They are usually named using nouns such as 'supervisor', 'manager', or 'team leader'.
The operator clients and the REST API allow you to create a link between two cardholders, called a relationship, using a role. The link is directional: we refer to the cardholder who 'has' the role as the child, and the cardholder who performs or 'is' the role as the parent.
A parent can perform a role for any number of child cardholders, but a child can only have one relationship (parent) for each role. Command Centre will reject your submission if you try to create a relationship when one already exists for the same child and role.
For example, a role on your system might be 'supervisor'. A cardholder can be a supervisor for any number of others, but will only have one supervisor.
Loops are possible: two cardholders can supervise each other, for example.
The REST API manages relationships through the child cardholder. You can create them at the same time as creating the child in a POST, or add them later in a PATCH.
There is currently no way to list all a cardholder's children in one request (everyone a particular cardholder is supervising, for example). You would achieve that by iterating through all cardholders, after re-reading the efficiency tips, and checking their relationships - quite easily done in JSONPath.
This section describes the object you receive in a cardholder's detail page and you will send in a POST or PATCH. The child cardholder does not appear in it because he or she is identified by the URL of the request.
hrefstring<uri-reference>DELETE this link, or put it in relationships.remove.href of a PATCH, to sever the
relationship between the two cardholders. Specify it in relationships.update.href of a
PATCH to update the relationship.
Do not specify it when creating a new relationship.
DELETE is the only verb you can use on this URL. GET will always return a 404.
roleobjectThis is the role that the parent identified in the next block performs for the cardholder identified by the request URL.
Once set, this cannot be changed. Command Centre will ignore it if you send it in an update PATCH. If you need to swap a parent from one role to another, send an add and a delete in the same PATCH.
cardholderobjectThe href and name of the cardholder that performs this role.
The three name fields are read-only: Command Centre sends them to you in the body of a GET but will ignore them if you send them in the body of a POST or PATCH.
This block and the href in it are unnecessary when deleting a relationship. The href is required when creating one, and optional when updating, but strongly advised since the cardholder is the only thing about a relationship you can change.
firstName and lastName appeared in 8.20.
{
"href": "https://host.com:8904/api/cardholders/325/relationships/179lah1170",
"role": {
"href": "https://host.com:8904/api/roles/5396",
"name": "Supervisor"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398",
"name": "Miles Messervy",
"firstName": "Miles",
"lastName": "Messervy"
}
}CardholderLocker
objectThese appear in an array in a cardholder detail, showing the cardholder's allocated lockers.
Each allocation has from and until dates, much like cards and access group memberships,
outside of which the allocation is inactive.
A locker can have allocations to more than one cardholder, with separate or overlapping periods. Unlike access groups, however, one cardholder cannot have more than one allocation to the same locker.
hrefstring<uri-reference>DELETE this to end a cardholder's use of a locker, or use it to identify the allocation you wish to modify in a cardholder PATCH.
DELETE is the only verb you can use on this URL. GET will always return a 404.
lockerobjectThis href in this object is a link to the allocated locker, and is the identifier to use when allocating the same locker to another cardholder.
The object also contains the name and short name of the locker, and the name and identifying href of its bank.
fromstring<date-time>The start of the time period during which the cardholder has access to this locker.
Send an empty string "" to reset it.
untilstring<date-time>The end of the time period during which the cardholder has access to this locker.
Send an empty string "" to reset it, making the allocation permanent.
{
"href": "https://host.com:8904/api/cardholders/325/lockers/t1m4",
"locker": {
"name": "Bank A locker 1",
"shortName": "A1",
"lockerBank": {
"href": "https://host.com:8904/api/locker_banks/4567",
"name": "Bank A"
},
"href": "https://host.com:8904/api/lockers/3456"
},
"from": "2017-01-01T00:00:00Z",
"until": "2018-12-31T00:00:00Z"
}CardholderElevatorGroup
objectThese appear in an array in a cardholder detail, showing the cardholder's elevator group properties, and can go into the body of a POST or PATCH to set a cardholder's elevator group properties.
A cardholder can have one default floor per elevator group. The elevator system will prepare a car to carry that cardholder to the access zone shown here when the cardholder badges a card at an appropriately configured kiosk.
Elevator system features can be activated by enabling the feature for an elevator group. For example, 'VIP features' gives exclusive access to an elevator car to the passenger.
hrefstring<uri-reference>The href of this cardholder's elevator group entry. DELETE this to remove it from the cardholder.
elevatorGroupobjectThe href and name of the elevator group for which this cardholder has a default floor or passenger type.
accessZoneobjectThe href and name of the access zone (floor) to which this cardholder is most likely to want to travel after entering the group's main elevator lobby. This property will be missing if the cardholder does not have a default floor for this elevator group.
enableCaptureFeaturesbooleanCardholders can select and recall specific elevators to specific floors using a kiosk. Once captured, the elevator car can be placed on independent service to give users control of the car to clean the interior or perform maintenance.
enableCodeBlueFeaturesbooleanA special elevator mode which is commonly found in hospitals. It allows an elevator to be summoned to any floor for use in an emergency situation.
enableExpressFeaturesbooleanAllows the cardholder to program selected elevators to cycle continuously between two floors for a pre-determined duration. For example, this feature can help hotels transport food efficiently from their kitchen to a ballroom on another floor. You can also prevent other guests from boarding to provide your banquet guests with VIP treatment.
enableServiceFeaturesbooleanService personnel can use this function to call an empty elevator and ride it nonstop to their destination floor. The user simply registers a call via a card swipe or PIN entry that is pre-programmed to grant access.
enableService2FeaturesbooleanService personnel can use this function to call an empty elevator and ride it nonstop to their destination floor. The user simply registers a call via a card swipe or PIN entry that is pre-programmed to grant access.
enableService3FeaturesbooleanService personnel can use this function to call an empty elevator and ride it nonstop to their destination floor. The user simply registers a call via a card swipe or PIN entry that is pre-programmed to grant access.
enableVipFeaturesbooleanVIP operation allows cardholders to swipe a card or enter a PIN to isolate the elevator and provide uninterrupted access to their designated floor.
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/567",
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635",
"name": "Main building lower floors"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637",
"name": "Lvl 1 lift lobby"
},
"enableCaptureFeatures": true,
"enableCodeBlueFeatures": false,
"enableExpressFeatures": true,
"enableServiceFeatures": false,
"enableService2Features": true,
"enableService3Features": true,
"enableVipFeatures": false
}CardholderPOST
objectThis is part of the model of a POST body that creates a cardholder. It does not contain every field you can set when creating a cardholder, but shows the division (which is mandatory) and an access group, an access card, a supervisor, a competency, two personal data fields, two lockers, and two elevator groups.
There are more fields on a cardholder than shown here. For a complete list please see the schema for the detailed cardholder object that you receive from a cardholder href.
firstNamestringYou must supply either this or the last name when creating a cardholder.
The maximum length of a cardholder's first name in version 9.50 is 50 characters.
lastNamestringYou must supply either this or the first name when creating a cardholder.
The maximum length of a cardholder's last name in version 9.50 is 50 characters.
shortNamestringdescriptionstringauthorisedbooleanRemember to set this true, as shown here in the POST, or later in a PATCH. Otherwise your new cardholder will never get through a door.
divisionobjectrequiredMandatory when creating any cardholder. In this example, we want all students in division 5387.
@emailstringAn example PDF value. In this example, access group 352 must include a PDF called 'email' otherwise this will appear to have no effect.
@headshotstringAn example image PDF encoded to Base64. Access group 352 must include this PDF as well.
As a quick visual check on your encoding, JPEGs start with /9j/.
personalDataDefinitionsArray<CardholderPDF>This is how you set and unset the notifications flags on PDFs. If you do not do it here when you first give a cardholder a PDF, it will come from the PDF's definition.
cardsArray<CardholderCard>This example creates one physical card of type 600 with a PIN and a system-generated card number, and another of type 654 which--judging by the invitation block--must be a mobile credential. Command Centre will send an invitation to Nick at those coordinates.
Card PINs were added to the API in 8.90.
accessGroupsArray<CardholderAccessGroup>Here you can add the access groups necessary to give your new cardholder the PDFs and access he or she needs. In this example we set the activation date of the access group membership to the first of January 2019.
operatorGroupsArray<CardholderOperatorGroup>Here you can add the operator groups necessary to give your new cardholder the software access he or she needs. Added in 8.50.
competenciesArray<CardholderCompetency>In this example we are giving our new cardholder a disabled competency, set to enable in January 2019.
notesstringnotificationsobjectYou can set or update any of the three fields in this block.
relationshipsArray<CardholderRelationship>Here you would set the cardholders who will perform roles for this cardholder.
The example shows that this cardholder will have cardholder 5398 performing role 5396.
Remember that you can supply an array of these objects if your cardholder is having more than one role filled.
lockersArray<CardholderLocker>Here you set all the cardholder's locker assignments. Ensure you are only attempting to give your cardholder a locker according to site policy (one locker per locker bank, for example), otherwise the POST will fail.
The example shows our cardholder receiving two lockers.
elevatorGroupsArray<CardholderElevatorGroup>Here you set all the new cardholder's default elevator floors and passenger types. Passenger type properties are false by default.
The example shows our cardholder receiving a default floor for the first elevator group, with the Code Blue feature enabled in the second group.
Added in 8.50.
{
"firstName": "Algernon",
"lastName": "Boothroyd",
"shortName": "Q",
"description": "Quartermaster",
"authorised": true,
"division": {
"href": "https://host.com:8904/api/divisions/5387"
},
"@email": "user@sample.com",
"@headshot": "/9j/4A...==",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
}
],
"cards": [
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/654"
},
"number": "Nick's mobile",
"invitation": {
"email": "nick@example.com",
"mobile": "02123456789"
}
}
],
"accessGroups": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
},
"from": "2019-01-01T00:00:00Z"
}
],
"operatorGroups": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/523"
}
}
],
"competencies": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2019-01-01T00:00:00Z"
}
],
"notes": "",
"notifications": {
"enabled": true,
"from": "2017-10-10T14:59:00Z",
"until": "2017-10-17T14:59:00Z"
},
"relationships": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"lockers": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/3456"
}
},
{
"locker": {
"href": "https://host.com:8904/api/lockers/3457"
}
}
],
"elevatorGroups": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
},
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/639"
},
"enableCodeBlueFeatures": true
}
]
}CardholderPATCH
objectSend one of these in a PATCH to /api/cardholders/{id} to modify the cardholder at that
URL, or to add and modify cards, competencies, personal data, group memberships, and
relationships.
This model only shows some of the fields available on a cardholder. The full list is in the cardholder detail.
authorisedbooleanfirstNamestring@employeeIdstringReplace PDF values as though they were flat fields on a cardholder. Prefix the name of
the PDF with @, and Base64-encode images. Send 'null' if you want to delete a PDF value.
Remember that a cardholder's record will not return a PDF if they are not a member of an access group that grants that PDF. Current versions of Command Centre are tolerant of your attempts to change the PDF in that situation, but future versions may not be.
personalDataDefinitionsArray<CardholderPDF>This is how you set and unset the notifications flags on PDFs.
cardsobjectThis object can contain three arrays, named add, update, and remove. Every element
you put in those arrays should be in the card schema.
Each element of the add array will need a type member, at the very least. Every card
field makes sense here except href. Only existing cards have hrefs. This example adds
two cards: one has nothing more than the type, so it will receive blank from and
until dates and a computed number and issue level. The other is a mobile credential (it
has an invitation block) with a custom initial state.
Each element of the update array should be a card to modify. It will need the href of
that card, plus the fields you want to change. Remember you cannot change a card's type.
The example changes the issue level and resets the until date.
The only field that makes sense in an element of the remove array is href and status.
You can remove and add cards in the same PATCH. In fact you should do that in preference
to making multiple API calls. That is a good way of reissuing a mobile credentials, for
example: put the href to the old one in the remove array and a new invitation in the
add array. The new credential should have the same card number and the same type and
invitation blocks as the credential you're re-issuing.
Do not put the same href in both the update and remove arrays.
In version 8.90 and later you can specify a card state in the value field inside the
status block when removing a card or changing its issue level. It becomes the final
state of the card if you remove it or the final state of the card with the previous issue
level if you re-issue it, so it must be one of the valid states for the card type. The
Gallagher clients and the resulting event call this state the 'reason' for the re-issue or
removal. By default it will be the same as the card's current state.
accessGroupsobjectLike the cards object, this can contain three arrays named add, update, and
remove. Every element you send in those arrays should be in the access group
schema.
This operation does not modify access groups in Command Centre; it works on a cardholder's memberships to those groups.
Each element of the add array will need an accessGroup member containing an href
identifying the group to which you wish to add the cardholder. The other fields are
optional. If you omit from, the membership will take effect immediately. If you omit
until, it will be unending.
Each element of the update array will need an href identifying the membership (not the group!) to update,
and one or both of the from and until date-times with new values. You cannot
change the group: just the activity period. The example removes the until date, effectively making
the group membership unending.
In version 7.90.883 or earlier, from and until should be in UTC with a
trailing 'Z'. Releases after 883 understand different timezones here.
Note that updating a cardholder's group membership will change its href, so do not cache it.
The only field that makes sense in an element of the remove array is the href.
Do not put the same href in both the update and remove arrays.
competenciesobjectLike the cards and accessGroups objects, this can contain three arrays named add,
update, and remove. Every element should be in the cardholder competency
schema.
This operation does not modify Command Centre's competencies: it works on a cardholder's holdings of those competencies.
Each element of the add array will need a competency block containing an href member
identifying the competency you wish to grant the cardholder. All other fields are optional.
Note that attempting to give a cardholder a competency he or she already has
will result in an error or an alarm depending on the server version.
Each element of the update array will need an href identifying the
cardholder/competency link to update, and one or more of the expiry, enabled,
enablement, comment, limitedCredit, and credit fields.
The only field that makes sense in an element of the remove array is the href
of the link between the cardholder and the competency.
Do not put the same href in both the update and remove arrays.
In this example we are giving the cardholder a disabled competency which will enable in 2021, and activating another competency.
relationshipsobjectIt should be no surprise that this can contain three arrays named add, update, and
remove, and that every element should be in the relationship
schema.
This operation does not modify Command Centre's roles: it works on the relationships between two cardholders.
Each element of the add array will need a role member containing an href identifying
the type of relationship you wish to establish, and a cardholder member containing an
href identifying the other party (the one who will perform the role for the cardholder at
the URL you are PATCHing). There are no optional fields.
Each element of the update array will need an href identifying the relationship to
update, and one or both of the role and cardholder blocks containing the updated
values. The example leaves the role but changes the cardholder - a new supervisor,
presumably.
The only field that makes sense in an element of the remove array is the href.
Do not put the same href in both the update and remove arrays.
lockersobjectWith you well in the habit by now, each element of your three arrays should be in the cardholder locker schema. They will allocate lockers to cardholders, de-allocate them, and adjust validity periods.
Each member of the add array will need a locker member containing an href identifying
the locker to allocate. The cardholder you will allocate it to is identified by the
request URL, remember.
Each member of the update array will need an href identifying the allocation to update,
and one or both of the from and until date-times.
The validity period is all you can change about a locker allocation. If you want to
change the locker, delete the old one and add a new. You can do that in the same PATCH,
with one element in each of the add and remove arrays.
This example allocates one locker starting in January 2019, sets the end-date of an existing allocation to the end of February 2020, and removes another entirely.
operatorGroupsobjectThis can contain two arrays named add and remove. Every element you send in those
arrays should be in the cardholder operator group
schema.
Each element of the add array will need an operatorGroup member containing an href
identifying the operator group to which you wish to add the cardholder. Supported in 8.50
and later.
The only field that makes sense in an element of the remove array is the href. Make
sure it is the href of the membership, not of the operator group. Supported in 8.90 and
later.
There is nothing about an operator group membership that you can change so there is no
point to an update array. Operator group memberships are, or are not: there is no
update.
elevatorGroupsobjectThis can contain three arrays named add, update, and
remove, each containing an element in the cardholder elevator group
schema.
The elevatorGroup block only makes sense in the add array. The update array is for
changing the cardholder's default floor and passenger types on an existing elevator group
assignment.
The server will ignore all fields in the elevatorGroup and accessZone objects except href
if you place them in the body of your PATCH.
Remove the default floor in an update by supplying an accessZone block with the href set to null or "".
Change a passenger type in an update by supplying the new value. The passenger type will be unchanged otherwise.
As with cards and access group memberships etc., the server ignores root-level hrefs in
the elements of an add array, requires them in the update array (since they indicate
the entries to work on), and ignores everything but them in the remove array.
The example shows our cardholder receiving a default floor for one elevator group and updating the Code Blue and VIP passenger types for another elevator group.
Added in 8.50.
{
"authorised": true,
"firstName": "Erica",
"@employeeId": "THX1139",
"personalDataDefinitions": [
{
"@email": {
"notifications": true
}
},
{
"@cellphone": {
"notifications": false
}
}
],
"cards": {
"add": [
{
"type": {
"href": "https://host.com:8904/api/card_types/354"
},
"pin": "153624"
},
{
"type": {
"href": "https://host.com:8904/api/card_types/600"
},
"number": "Jock's iPhone 8",
"status": {
"value": "Pending sign-off"
},
"invitation": {
"email": "jock@example.com"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
"issueLevel": 2,
"until": "",
"status": {
"value": "Stolen"
},
"pin": "153624"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
"status": {
"value": "Lost"
}
}
]
},
"accessGroups": {
"add": [
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/352"
}
},
{
"accessGroup": {
"href": "https://host.com:8904/api/access_groups/124"
},
"until": "2019-12-31"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ad21",
"until": ""
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/access_groups/10ed27"
}
]
},
"competencies": {
"add": [
{
"competency": {
"href": "https://host.com:8904/api/competencies/2354"
},
"enabled": false,
"enablement": "2021-01-01T08:00+13"
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/competencies/2dc3",
"enabled": true
}
]
},
"relationships": {
"add": [
{
"role": {
"href": "https://host.com:8904/api/roles/5396"
},
"cardholder": {
"href": "https://host.com:8904/api/cardholders/5398"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/roles/1799lah1170",
"cardholder": {
"href": "https://host.com:8904/api/cardholders/10135"
}
}
]
},
"lockers": {
"add": [
{
"locker": {
"href": "https://host.com:8904/api/lockers/1200",
"from": "2019-01-01"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/wxyz1234",
"until": "2020-02-29"
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/lockers/abcd4321"
}
]
},
"operatorGroups": {
"add": [
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/532"
}
},
{
"operatorGroup": {
"href": "https://host.com:8904/api/operator_groups/535"
}
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/operator_groups/EBDRSD"
}
]
},
"elevatorGroups": {
"add": [
{
"elevatorGroup": {
"href": "https://host.com:8904/api/elevator_groups/635"
},
"accessZone": {
"href": "https://host.com:8904/api/access_zones/637"
}
}
],
"update": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/1268613268",
"enableCodeBlueFeatures": true,
"enableVipFeatures": false
}
],
"remove": [
{
"href": "https://host.com:8904/api/cardholders/325/elevator_groups/3498734"
}
]
}
}CardholderChanges
objectAn array of cardholder changes, described in the next section, and a next link for more.
resultsArray<CardholderChange>An array of cardholder changes.
nextobjectThe link to the next page of changes. This will always be present, because (unlike items) changes never run out.
Because the next link is a pointer to the head of a queue of changes, and new changes
are being added to that queue which will not suit your filter or privileges, it will
change even when there are no results.
Therefore you should always use this link for your next query. Do not be tempted to
re-use a URL after results comes back empty, thinking you merely need to ask the same
question again. Doing that will cause the server unnecessary work, skipping over changes
that did not pass your filter or privilege checks on the previous call.
{
"results": [
{
"href": "https://host.com:8904/api/cardholders/changes/f4e67a",
"time": "2020-01-14T03:14:33Z",
"type": "update",
"item": {
"href": "https://host.com:8904/api/cardholders/525"
},
"operator": {
"name": "System Operator",
"href": "https://host.com:8904/api/items/1"
},
"oldValues": {
"firstName": "Craig",
"competencies": [
{
"enablement": "",
"href": "https://host.com:8904/api/cardholders/525/competencies/3910e4"
}
]
},
"newValues": {
"firstName": "Gavin",
"competencies": [
{
"enablement": "2020-02-29T00:00:00Z",
"href": "https://host.com:8904/api/cardholders/525/competencies/3910e4"
}
],
"cards": [
{
"number": "2",
"cardSerialNumber": "",
"issueLevel": 1,
"from": "",
"until": "",
"href": "https://host.com:8904/api/cardholders/525/cards/285f779af1ef49abbba"
}
],
"accessGroups": [
{
"accessGroup": {
"name": "Access Group 1",
"href": "https://host.com:8904/api/access_groups/499"
},
"from": "",
"until": "2020-01-15T04:39:00Z",
"href": "https://host.com:8904/api/cardholders/525/access_groups/f9cb328b4"
}
]
},
"cardholder": {
"firstName": "Gavin",
"cards": [
{
"number": "2",
"cardSerialNumber": "",
"issueLevel": 1,
"from": "",
"until": "",
"href": "https://host.com:8904/api/cardholders/525/cards/285f779af1ef49abbba"
}
]
}
}
],
"next": {
"href": "https://host.com:8904/api/cardholders/changes?pos=SWEp9"
}
}CardholderChange
object/api/cardholders/changes returns an array of these. Each contains a description of a change
made to a cardholder.
In this example a cardholder has had his name changed from Craig to Gavin, has had an enablement date set on a competency, and has had a card and access group membership added.
Notes
Changes to an access group membership will have a different href in the
oldValuesandnewValuesblocks, because modifying a group membership changes its ID.The API will not notify changes to a competency's
creditinteger orlimitedCreditboolean. These fields are part of a separate Command Centre feature and are not supported by the changes API.The API will report a spurious change to a PDF value when a cardholder rejoins an access group he or she was previously a member of, provided that access group carried a PDF with no default and the cardholder did not have a value for it.
This API will not notify changes to
lastSuccessfulAccessTimeorlastSuccessfulAccessZone.
hrefstring<uri-reference>Command Centre's identifier for this change. This has no use in the API: you cannot use it as a URL, but you may like to use it to track the changes you have seen.
timestring<date-time>The time that this change occurred.
typestringaddupdateremoveadd if this change added a cardholder, update if it modified a cardholder, or remove
if it deleted a cardholder.
itemobjectA block containing the href of the changed cardholder. You can GET this URL to find the
cardholder's current state, or you could add the cardholder block to the change using
fields=cardholder in the query. Note that you can add individual cardholder fields such
as PDFs using fields=defaults,cardholder.personalDataFields.
operatorobjectA block containing the href and current name of the operator who made this change.
Because building this block requires more work from the server it is not in the default
field set. If you need it you must ask for it using the fields parameter:
fields=defaults,operator.
oldValuesanyA block containing the values of the changed fields before the change, if Command Centre still has them, in the same format as a cardholder detail. If a value is blank, it means that the value was null before the change or the server no longer has it.
Because this requires extra effort from the server, you may like to omit this block using
something like fields=time,type,item,operator unless you are particularly interested in
historical data.
newValuesanyA similar block containing the values of the changed fields after the change, if the server has them.
Generally, this block is less useful than the current state of the cardholder, described
next. It also requires extra effort from the server, so you may like to omit this block
using fields.
cardholderanyA block containing the current fields on the cardholder, provided the cardholder has not been deleted. This is in the same format as a cardholder detail.
Because building this requires more work from the server it is not in the default result
set. If you need it you must ask for it using the fields parameter. While the server
has a default field set for cardholders, your query will be more efficient if you ask for
just the fields you need:
fields=defaults,cardholder.firstName,cardholder.lastName,cardholder.cards, etc. Note
how you must prefix each field with cardholder since they are all inside a block with
that name.
{
"href": "https://host.com:8904/api/cardholders/changes/f4e67a",
"time": "2020-01-14T03:14:33Z",
"type": "update",
"item": {
"href": "https://host.com:8904/api/cardholders/525"
},
"operator": {
"name": "System Operator",
"href": "https://host.com:8904/api/items/1"
},
"oldValues": {
"firstName": "Craig",
"competencies": [
{
"enablement": "",
"href": "https://host.com:8904/api/cardholders/525/competencies/3910e4"
}
]
},
"newValues": {
"firstName": "Gavin",
"competencies": [
{
"enablement": "2020-02-29T00:00:00Z",
"href": "https://host.com:8904/api/cardholders/525/competencies/3910e4"
}
],
"cards": [
{
"number": "2",
"cardSerialNumber": "",
"issueLevel": 1,
"from": "",
"until": "",
"href": "https://host.com:8904/api/cardholders/525/cards/285f779af1ef49abbba"
}
],
"accessGroups": [
{
"accessGroup": {
"name": "Access Group 1",
"href": "https://host.com:8904/api/access_groups/499"
},
"from": "",
"until": "2020-01-15T04:39:00Z",
"href": "https://host.com:8904/api/cardholders/525/access_groups/f9cb328b4"
}
]
},
"cardholder": {
"firstName": "Gavin",
"cards": [
{
"number": "2",
"cardSerialNumber": "",
"issueLevel": 1,
"from": "",
"until": "",
"href": "https://host.com:8904/api/cardholders/525/cards/285f779af1ef49abbba"
}
]
}
}CardTypeSearch
objectAn array of card types, and a next link for more. Sites generally have only a handful of
card types, so when retrieving them you should set your 'top' parameter high enough that you
do not need the 'next' link.
resultsArray<CardType>An array of card types.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}
],
"next": {
"href": "https://host.com:8904/api/card_types/assign?skip=1000"
}
}CardType
objectThis object describes a single Card Type. "Credential type" would be a better name, as Command Centre's card types include credentials that do not require a plastic card.
hrefstring<uri-reference>read onlyidstringread onlyThe API does not use this field. Nor should you.
namestringdivisionobjectThe division that contains this card type. Required when creating a card type.
notesstringFree text.
Because of its potential size, the server does not return the notes field by default. You
need to ask for it with fields=notes.
facilityCodestringA facility code is a letter (A-P) followed by up to five digits. It is encoded onto cards so that they only work at sites with the correct facility code.
PIV cards, PIV-I cards, and mobile credentials do not have a facility code.
Most credential classes require a facility code on creation. But it cannot be changed once set, so this field is ignored in a PATCH.
availableCardStatesArray<string>ActiveDisabled (manually)LostStolenDamagedread onlyAll credential types have a set of card states.
If you need this, ask for it using the fields parameter.
This field is read-only: it is derived from the card type's card state set (also known as a workflow). If you send it in a POST or a PATCH, the server will ignore it.
credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransactRequired when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.
minimumNumberstringFor card types with integer card numbers, this is the minimum. Must be non-negative.
maximumNumberstringFor card types with integer card numbers, this is the maximum. Must be non-negative.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
regexstringThis is the regular expression that a text card number must match before Command Centre will accept it.
regexDescriptionstringRegular expressions often need explaining to your users.
cardStateSetanyDocumentation coming. TODO
POST only.
defaultIssueLevelanyDocumentation coming. TODO
cardIssueLevelMustMatchDefaultanyDocumentation coming. TODO
enableExpiryNotificationsanyDocumentation coming. TODO
warningPeriodanyDocumentation coming. TODO
pinLengthanyDocumentation coming. TODO
inactivityTimeoutInDaysanyDocumentation coming. TODO
allowReprintanyDocumentation coming. TODO
allowReEncodeanyDocumentation coming. TODO
isEngageanyDocumentation coming. TODO
sendRegistrationEmailanyDocumentation coming. TODO
sendRegistrationSmsanyDocumentation coming. TODO
warningPeriodTypeanyDocumentation coming. TODO
Days, Weeks, Months, or Years.
cardNumberFormatanyDocumentation coming. TODO
POST only.
defaultExpiryTypeanyDocumentation coming. TODO
ExplicitDateTime or Duration.
defaultExpiryDurationanyDocumentation coming. TODO
(if the type is duration)
defaultExpiryFromanyDocumentation coming. TODO
defaultExpiryUntilanyDocumentation coming. TODO
(if defaultExpiryType is explicitDateTime)
engageCardFormatanyDocumentation coming. TODO
Only if isEngage is true. There are nine acceptable values.
appleTemplateIdanyDocumentation coming. TODO
Required if credential class is Apple Pass.
appleLinkedCredentials |anyDocumentation coming. TODO
The POST format is different from the PATCH format's add and remove arrays.
{
"href": "https://host.com:8904/api/card_types/600",
"id": "600",
"name": "Red DESFire visitor badge",
"division": {
"href": "https://host.com:8904/api/divisions/2"
},
"notes": "Disabled after 7d inactivity, 6-char PIN",
"facilityCode": "A12345",
"availableCardStates": [
"Active",
"Disabled (manually)",
"Lost",
"Stolen",
"Damaged"
],
"credentialClass": "card",
"minimumNumber": "1",
"maximumNumber": "16777215",
"serverDisplayName": "ruatoria.satellite.int",
"regex": "^[A-Za-z0-9]+$",
"regexDescription": "Only alphanumeric characters"
}CompetencySearch
objectAn array of competency summaries, and a next link for more.
resultsArray<CompetencySummary>An array of competency summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/competencies/2354",
"id": "2354",
"name": "Hazardous goods handling",
"description": "Required for access to chem sheds.",
"serverDisplayName": "ruatoria.satellite.net",
"notes": ""
}
],
"next": {
"href": "https://localhost:8904/api/competencies?skip=1000"
}
}CompetencySummary
objectThe competency search at /api/competencies returns an array of these. The object contains
some of what you get from a competency's detail page at /api/competencies/{id}, linked as
the href in this object.
{
"href": "https://localhost:8904/api/competencies/2354",
"id": "2354",
"name": "Hazardous goods handling",
"description": "Required for access to chem sheds.",
"serverDisplayName": "ruatoria.satellite.net",
"notes": ""
}CompetencyDetail
object/api/competencies/{id} returns one of these. It contains the same fields you see when you view or edit a competency in the Configuration client.
The competency search at /api/competencies returns an array of these. The object contains
some of what you get from a competency's detail page at /api/competencies/{id}, linked as
the href in this object.
divisionobjectThe division containing this competency.
shortNamestringexpiryNotifybooleanSet if notifications should go out before a cardholder loses this competency. How long before is in the 'noticePeriod' block. Who the notification goes to, in addition to the cardholder holding the competency, depends on the cardholder's relationships and what notifications are set on the relationship's role.
noticePeriodanyHow long before expiry Command Centre should send its notification and display 'expiry due' messages on display devices such as a T20 and through this API.
A zero-length notice period means there will be no warning notification.
defaultExpiryobjectIn this block, 'expiryType' and 'expiryValue' tell you the expiry time Command Centre will use if the operator does not specify one when assigning this competency to a cardholder. It can be unset (meaning the assignment will be unending), a fixed date, or a time period.
In this example, cardholders will hold the 'Hazardous goods handling' competency for six months after an operator first gives it to them. In practice, the operator should enter the closest end date of the qualifications that allow them to handle hazardous goods. Safety and first aid training, presumably.
defaultAccessstringnoAccessreadOnlyfullAccessfullAccessThis is the access that operators will have to cardholders' assignments of this competency if the operator is not a member of an operator group that overrides it. Check the operator group's 'Competency' tab in the Configuration Client.
{
"href": "https://localhost:8904/api/competencies/2354",
"id": "2354",
"name": "Hazardous goods handling",
"description": "Required for access to chem sheds.",
"serverDisplayName": "ruatoria.satellite.net",
"notes": "",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "",
"expiryNotify": false,
"noticePeriod": {
"units": "weeks",
"number": 2
},
"defaultExpiry": {
"expiryType": "durationmonths",
"expiryValue": 6
},
"defaultAccess": "fullAccess"
}Divisions
objectCalls inside /api/divisions/ return this object, which is simply a named array of
objects each containing some information about a division.
resultsArray<Division>An array of division objects.
nextobjectA site generally does not have too many divisions, but if yours does and you do not get
them all in the first page, follow this link to get more. Repeat until the results do
not contain a next link.
{
"results": [
{
"href": "https://localhost:8904/divisions/2",
"id": "2",
"name": "Root division",
"description": "Contains all other divisions",
"serverDisplayName": "ruatoria.satellite.int",
"parent": {
"href": "https://localhost:8904/divisions/2"
},
"visitorManagement": {
"active": true,
"visitorTypes": [
{
"href": "https://localhost:8904/api/divisions/2/visitor_types/925",
"accessGroup": {
"name": "Visitor access group 1",
"href": "https://localhost:8904/api/access_groups/925"
},
"hostAccessGroups": [
{
"accessGroup": {
"name": "Host access group 1",
"href": "https://localhost:8904/api/access_groups/938"
}
}
],
"visitorAccessGroups": [
{
"accessGroup": {
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
},
{
"accessGroup": {
"name": "Access group 30",
"href": "https://localhost:8904/api/access_groups/927"
}
}
]
}
]
}
}
],
"next": {
"href": "https://localhost:8904/api/divisions/view_events?skip=10"
}
}Division
objectWhen a REST call returns the division of a Command Centre item such as a cardholder or access
group, or when you ask it for the divisions in which an operator has a particular privilege,
it will give an href inside /divisions/. Following that href will return one of these.
hrefstring<uri-reference>A self reference.
idstringThe alphanumeric ID of the division. Use this ID in the division filter when requesting
events.
namestringThe division's name.
descriptionstringThe division's description. New in 8.50. Not sent by default; ask for it with fields.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
parentobjectAn object containing an href to the division entity representing the current division's parent.
All divisions except the root division have a parent.
visitorManagementobjectAn object containing the division's visitor management configuration. It only appears if
you ask for it with fields=visitorManagement and if your operator has the necessary
privilege ('View Site', 'Edit Site', 'View Visits', 'Edit Visits', or 'Manage
Receptions').
{
"href": "https://localhost:8904/divisions/2",
"id": "2",
"name": "Root division",
"description": "Contains all other divisions",
"serverDisplayName": "ruatoria.satellite.int",
"parent": {
"href": "https://localhost:8904/divisions/2"
},
"visitorManagement": {
"active": true,
"visitorTypes": [
{
"href": "https://localhost:8904/api/divisions/2/visitor_types/925",
"accessGroup": {
"name": "Visitor access group 1",
"href": "https://localhost:8904/api/access_groups/925"
},
"hostAccessGroups": [
{
"accessGroup": {
"name": "Host access group 1",
"href": "https://localhost:8904/api/access_groups/938"
}
}
],
"visitorAccessGroups": [
{
"accessGroup": {
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
},
{
"accessGroup": {
"name": "Access group 30",
"href": "https://localhost:8904/api/access_groups/927"
}
}
]
}
]
}
}DivisionPATCHAndPOSTExample
objectThis is an example of a PATCH you could use to update a division, and a POST you could use to create one.
When POSTing, parent is mandatory.
namestringThe division's name. If you supply a name and another division already exists with that name, the call will fail. If you leave it blank in a POST, Command Centre will pick value for you.
descriptionstringThe division's description.
notesstringA string, able to be much longer than description, suitable for holding notes about
the division.
parentobjectAn object containing an href to the division entity representing the current division's parent.
Required when creating a new division, because only root divisions can be unparented and you cannot create a new one of those.
{
"name": "Long division",
"description": "Quatermasters",
"notes": "A very long string.",
"parent": {
"href": "https://localhost:8904/api/divisions/2"
}
}DoorSearch
objectAn array of door summaries, and a next link for more.
resultsArray<DoorSummary>An array of door summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/doors/332",
"id": "332",
"name": "Front door"
}
],
"next": {
"href": "https://localhost:8904/api/doors?skip=1000"
}
}DoorSummary
object/api/doors returns an array of these. It is a subset of what you get from a
door's detail page at /api/doors/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to a door detail object for this door.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the
source parameter of event filters
when you are interested in events originating at this door.
namestring{
"href": "https://localhost:8904/api/doors/332",
"id": "332",
"name": "Front door"
}DoorDetail
object/api/doors/{id} returns one of these.
/api/doors returns an array of these. It is a subset of what you get from a
door's detail page at /api/doors/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to a door detail object for this door.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the
source parameter of event filters
when you are interested in events originating at this door.
namestringdescriptionstringdivisionobjectThe division containing this door.
entryAccessZoneobjectThe name and href of the access zone to which this door allows entry.
exitAccessZoneobjectThe name and href of the access zone to which this door allows entry in the reverse direction (from its exit reader).
It will not appear by default. You need to ask for it with fields=exitAccessZone.
New to 8.50.
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
updatesobjectFollow the URL in the href inside this block to receive the item's current status, then follow
the next link in the results to long poll for changes to that status.
This method only monitors one item at a time. To monitor the status of many items, use the status-monitoring routes.
Update pages take the same fields parameter as summary and details pages. You should use
that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's status
is unknown until something is monitoring it. If you want status flags on the search and
details pages you must ask for them using the fields parameter, but our advice is to monitor
them using status subscriptions if you are running 8.30 or
later, otherwise the item's updates link. See the item status section
for a full description of how to stay up to date with item status, and this item's
introduction in the Operations section for what flags this item might return and what they
mean.
commandsobjectAn array of commands, each represented by a block containing an href that accepts a POST to send an override.
If your operator is privileged to override the door's entry zone, and the zone only has one door, there will be 17 more links: four each of the four access zone modes, plus 'cancel'. Each zone mode has four variants: with or without PINs, and with or without an end time.
This block will only contain the links that your operator is privileged to perform. Examples of the privileges you need are in the documentation for the POSTs.
The zone overrides are not repeated here.
connectedControllerobjectThis block describes this door's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it, if your doors are legion.
{
"href": "https://localhost:8904/api/doors/332",
"id": "332",
"name": "Front door",
"description": "Main lobby doors.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"entryAccessZone": {
"name": "Roswell building 2 lobby",
"href": "https://localhost:8904/api/access_zones/3280"
},
"exitAccessZone": {
"name": "Roswell building 2 cafeteria",
"href": "https://localhost:8904/api/access_zones/50"
},
"notes": "Multi-line text...",
"shortName": "Short text",
"updates": {
"href": "https://localhost:8904/api/doors/332/updates/0_0_0"
},
"statusFlags": [
"secure",
"closed",
"locked"
],
"commands": {
"open": {
"href": "https://localhost:8904/api/doors/332/open"
},
"lock": {
"href": "https://localhost:8904/api/doors/332/lock"
}
},
"connectedController": {
"name": "Third floor C6000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
}
}ElevatorGroupSummary
objectThe elevator group search returns an array
of these, if you don't use the fields parameter to ask for more. It is a subset of what
you get from the elevator group's detail page (which is linked as the href in this object)
or the modify passenger details
search.
hrefstring<uri-reference>A link to a elevator group object for this elevator group.
namestring{
"href": "https://localhost:8904/api/elevator_groups/635",
"name": "Main building lower floors"
}ElevatorGroupFloorAccessDetail
objectThe modify passenger details search returns an array of these. It is everything you need to pick a default floor for a cardholder, but not everything you get from an elevator group's detail page (which is linked as the href in this object).
The elevator group search returns an array
of these, if you don't use the fields parameter to ask for more. It is a subset of what
you get from the elevator group's detail page (which is linked as the href in this object)
or the modify passenger details
search.
hrefstring<uri-reference>A link to a elevator group object for this elevator group.
namestringdivisionobjectThe division containing this elevator group.
floorAccessArray<object>An array of objects describing the floors in this elevator group.
This example only has one floor. Production elevator systems often have more.
{
"href": "https://localhost:8904/api/elevator_groups/635",
"name": "Main building lower floors",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"floorAccess": [
{
"floorNumber": 1,
"frontService": true,
"rearService": true,
"floorName": "Level 1",
"frontAccessZone": {
"id": "637",
"name": "Lvl 1 lift lobby",
"href": "http://localhost:8904/access_zones/637"
},
"rearAccessZone": {
"id": "638",
"name": "Lvl 1 lift lobby rear",
"href": "http://localhost:8904/access_zones/638"
}
}
]
}ElevatorGroupDetail
objectThe detail GET returns one of these.
The elevator group search returns an array
of these, if you don't use the fields parameter to ask for more. It is a subset of what
you get from the elevator group's detail page (which is linked as the href in this object)
or the modify passenger details
search.
hrefstring<uri-reference>A link to a elevator group object for this elevator group.
namestringdescriptionstringnotesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
elevatorGroupNumberintegerThe elevator system's internal identifier for this elevator group.
elevatorSystemobjectrearAccessEnabledbooleanTrue only if the elevator group uses rear doors on its cars to service the other side of the shaft.
groundFloorNumberinteger>= 1The identifier of the floor that this elevator group calls 'ground'.
{
"href": "https://localhost:8904/api/elevator_groups/635",
"name": "Main building lower floors",
"description": "Main building lobby elevator group.",
"notes": "Multi-line text...",
"shortName": "Short text",
"elevatorGroupNumber": 1,
"elevatorSystem": {
"id": "632"
},
"rearAccessEnabled": true,
"groundFloorNumber": 1
}EventSearch
object/api/events and /api/events/updates return this structure.
eventsArray<EventSummary>A list of event summaries.
previousobjectFollow this link to make a non-blocking call to collect the previous page of events (in order of arrival). It will return an empty page if you have already received the first event in the database.
nextobjectFollow this link to make a non-blocking call to collect the next page of events, moving forward in arrival time. It will return an empty page if no more events are available.
updatesobjectThis is a link to an updates call. Follow it to
make a blocking call to collect more events. If there are none, the call will block until
one arrives or the call times out. This link will contain your column select, pagination,
and filtering parameters (fields, top, after, before, source, type, etc.) but it drops
deadline. You need to add that to each call.
{
"events": [
{
"href": "https://localhost:8904/api/events/61320",
"id": "61320",
"serverDisplayName": "ruatoria.satellite.int",
"time": "2016-02-18T19:21:52Z",
"message": "Operator logon failed for FT Workstation on GNZ-PC1439",
"occurrences": 2,
"priority": 3,
"alarm": {
"state": "unacknowledged",
"href": "https://localhost:8904/api/alarms/61320"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Chong, Marc"
},
"source": {
"id": "321",
"name": "FT Workstation on GNZ-PC1439",
"href": "https://localhost:8904/api/items/321"
},
"group": {
"id": "35",
"name": "Invalid Logon"
},
"type": {
"id": "601",
"name": "Operator logon failed"
},
"eventType": {
"id": "601",
"name": "Operator logon failed"
},
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2",
"name": "Root division"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"id": "325",
"name": "Bruce, Jennifer",
"firstName": "Jennifer",
"lastName": "Caitlin"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Brookwood showroom",
"id": "333"
},
"exitAccessZone": {
"href": "https://localhost:8904/api/access_zones/913",
"name": "Compressor room",
"id": "913"
},
"door": {
"href": "https://localhost:8904/api/doors/745",
"name": "Main hoist door"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"card": {
"facilityCode": "A12345",
"number": "78745",
"issueLevel": 1
},
"modifiedItem": {
"href": "https://localhost:8904/api/cardholders/325",
"type": {
"id": "1",
"name": "Cardholder"
}
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"previous": {
"href": "https://localhost:8904/api/events?pos=61320&previous=True"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}
],
"previous": {
"href": "https://localhost:8904/api/events?previous=True&pos=61320"
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}EventSummary
object/api/events and /api/events/updates return an array of these, and /api/events/{id}
returns one with more fields.
The message and event type in this example indicate an operator attempting to log in with an incorrect password, but for the sake of illustration the example also contains references to items that would never appear on such an event. The card, cardholder, and access zones, for example.
hrefstring<uri-reference>A link to this event's details.
idstringAn alphanumeric identifier for this event, unique to the server.
serverDisplayNamestringThe host name of this event's origin server, if it was aggregated from a remote host. Absent for local events.
Note that this is not the descriptive name of the remote server's item, but the host name used for address resolution.
New to 8.40.
timestring<date-time>The time the event occurred.
messagestringThe event's message.
occurrencesinteger>= 2If an event arrives with the same essential properties as a previous event, the server will start counting them. Each event is still individually addressable and will appear in the API as normal, but the first in the group will also have this property. It only appears on the first, and it does not appear if the event is a singleton.
If present on an event's detail or alarm page, there will also be a lastOccurrenceTime.
Outside of the alarm block, which contains an alarm's state, this is the only field on
an event that can change.
priorityinteger[0, 9]Numeric priority. 9 is critical and 0 is not an event.
alarmobjectIf an event is also an alarm, this object will contain its state and a link to its details page in the alarms controller.
Only the first event in a group (see the occurrences field) can become an alarm.
operatorobjectThe href and name of the operator or system behind this event. This will appear when an operator has modified an item. New in v8.00.
The item he or she modified will appear in the modifiedItem block (added in 8.40).
Change is coming.
The operator may not be a cardholder. A version of Command Centre after 9.30 will
return events where the operator item is of a different kind. In that case, the
operator block will also contain a canonicalTypeName field telling you what kind of
item is at the other end of the href.
sourceobjectID and name of the source of the event, as recorded at the time of the event.
groupobjectID and name of the event group this event belongs to. Do not confuse this with an access group or operator group: this is the event type group to which the event's type belongs. There are about 150 and you can list them at /events/groups.
typeobjectID and name of the event's type. There is a long list of them at /events/groups.
eventTypeobjectID and name of the event's or alarm's type. There is a long list of them at /events/groups.
Unlike the type field, this has the same format in an event as it does in an alarm.
Added in 8.90. Because it is a new field, it does not appear by default. Ask for it using
the fields parameter.
divisionobjectID, name, and href of the event's division (which is the division of the event's source item, for most event types).
cardholderobjectSummary information about an event's cardholder, if there is one. This will be the
cardholder who badged their card at a door in an access event, or the cardholder an
operator modified in an operator event. Search for other events related to this example's
cardholder with cardholder=325 in the query parameters.
entryAccessZoneanyThe name and href of the entry access zone related to the event. In the case of card events, it is the zone into which a cardholder was attempting to gain access.
That is true for successful entries, successful exits, and access denials. Regardless of whether the cardholder badged at the entry or exit reader, this field refers to the zone that he or she attempted to access.
For example, for 'Card entry granted' events this field will contain the door's entry zone, but for 'Card exit granted' events this field will contain the door's exit zone, because 'exit granted' means the cardholder was in the door's entry zone, badged at its exit reader, and was granted access to its exit zone. Think of it as going through a door in its reverse direction.
exitAccessZoneanyThe name and href of the exit access zone related to the event. In card events, it is the zone from which a cardholder was attempting to leave, if the door had an exit zone configured (many do not).
That is true for successful entries, successful exits, and access denials. Regardless of whether the cardholder badged at the entry or exit reader, this field refers to the zone that he or she attempted to leave.
For example, for 'Card entry granted' events this field will contain the door's exit zone, if there was one, but for 'Card exit granted' events this field will contain the door's entry zone.
dooranyThe name and href of the door related to the event. These are not as common as you may
think, because when a door is relevant (to card events, for example) it is usually the
event's source, not its door, so it will be in the source block.
New in 8.10.
accessGroupobjectThe href of the access group that a cardholder just gained or lost in a 'Membership Activated' or 'Membership Expired' event. Those happen when a group membership's 'from' or 'until' time passes.
DEPRECATED for the events generated when an operator creates, modifies, or deletes an access group. Use the 'modifiedItem' field instead (new in 8.40).
cardobjectDetails of the card associated with the event.
Versions prior to 8.60 returned this block and a card number of zero for all access events, even if they did not involve a credential (after a person entered their user code at a keypad, for example). Version 8.60 does not return this block for such events.
modifiedItemobjectThe href and type of the item that an operator created, changed, or deleted, for those kinds of events.
New in 8.40.
nextobjectThe URL to the search that will return the page of events following this one. It will include your search filters and pagination parameters.
This link is intended for integrations that take great gulps of events and send them to a downstream system. If it suffers a problem in the middle of a result set, it needs to record where in the event trail it got up to so that when it restarts it will not miss or duplicate events.
New to 8.70.
previousobjectThe URL to the search that will return the page of events preceding this one. It will include your search filters and pagination parameters.
The previous link on the first event in a result set will be the same as the previous
link outside the results, since they both indicate the latest event that preceded the
result set.
New to 8.70.
updatesobjectThe URL to the updates call that will return the page of events following this one, or wait for one to arrive if there are none. It will include your search filters and pagination parameters.
New to 8.70.
{
"href": "https://localhost:8904/api/events/61320",
"id": "61320",
"serverDisplayName": "ruatoria.satellite.int",
"time": "2016-02-18T19:21:52Z",
"message": "Operator logon failed for FT Workstation on GNZ-PC1439",
"occurrences": 2,
"priority": 3,
"alarm": {
"state": "unacknowledged",
"href": "https://localhost:8904/api/alarms/61320"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Chong, Marc"
},
"source": {
"id": "321",
"name": "FT Workstation on GNZ-PC1439",
"href": "https://localhost:8904/api/items/321"
},
"group": {
"id": "35",
"name": "Invalid Logon"
},
"type": {
"id": "601",
"name": "Operator logon failed"
},
"eventType": {
"id": "601",
"name": "Operator logon failed"
},
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2",
"name": "Root division"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"id": "325",
"name": "Bruce, Jennifer",
"firstName": "Jennifer",
"lastName": "Caitlin"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Brookwood showroom",
"id": "333"
},
"exitAccessZone": {
"href": "https://localhost:8904/api/access_zones/913",
"name": "Compressor room",
"id": "913"
},
"door": {
"href": "https://localhost:8904/api/doors/745",
"name": "Main hoist door"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"card": {
"facilityCode": "A12345",
"number": "78745",
"issueLevel": 1
},
"modifiedItem": {
"href": "https://localhost:8904/api/cardholders/325",
"type": {
"id": "1",
"name": "Cardholder"
}
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"previous": {
"href": "https://localhost:8904/api/events?pos=61320&previous=True"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
}
}EventDetail
object/api/events/{id} returns one of these. It contains everything from the event summary results, plus some extra fields that are too expensive to compute and return for large result sets.
Like the example in the summary, this example is forced: no actual Command Centre event will contain all of these fields.
/api/events and /api/events/updates return an array of these, and /api/events/{id}
returns one with more fields.
The message and event type in this example indicate an operator attempting to log in with an incorrect password, but for the sake of illustration the example also contains references to items that would never appear on such an event. The card, cardholder, and access zones, for example.
hrefstring<uri-reference>A link to this event's details.
idstringAn alphanumeric identifier for this event, unique to the server.
serverDisplayNamestringThe host name of this event's origin server, if it was aggregated from a remote host. Absent for local events.
Note that this is not the descriptive name of the remote server's item, but the host name used for address resolution.
New to 8.40.
timestring<date-time>The time the event occurred.
messagestringThe event's message.
occurrencesinteger>= 2If an event arrives with the same essential properties as a previous event, the server will start counting them. Each event is still individually addressable and will appear in the API as normal, but the first in the group will also have this property. It only appears on the first, and it does not appear if the event is a singleton.
If present on an event's detail or alarm page, there will also be a lastOccurrenceTime.
Outside of the alarm block, which contains an alarm's state, this is the only field on
an event that can change.
priorityinteger[0, 9]Numeric priority. 9 is critical and 0 is not an event.
alarmobjectIf an event is also an alarm, this object will contain its state and a link to its details page in the alarms controller.
Only the first event in a group (see the occurrences field) can become an alarm.
operatorobjectThe href and name of the operator or system behind this event. This will appear when an operator has modified an item. New in v8.00.
The item he or she modified will appear in the modifiedItem block (added in 8.40).
Change is coming.
The operator may not be a cardholder. A version of Command Centre after 9.30 will
return events where the operator item is of a different kind. In that case, the
operator block will also contain a canonicalTypeName field telling you what kind of
item is at the other end of the href.
sourceobjectID and name of the source of the event, as recorded at the time of the event.
groupobjectID and name of the event group this event belongs to. Do not confuse this with an access group or operator group: this is the event type group to which the event's type belongs. There are about 150 and you can list them at /events/groups.
typeobjectID and name of the event's type. There is a long list of them at /events/groups.
eventTypeobjectID and name of the event's or alarm's type. There is a long list of them at /events/groups.
Unlike the type field, this has the same format in an event as it does in an alarm.
Added in 8.90. Because it is a new field, it does not appear by default. Ask for it using
the fields parameter.
divisionobjectID, name, and href of the event's division (which is the division of the event's source item, for most event types).
cardholderobjectSummary information about an event's cardholder, if there is one. This will be the
cardholder who badged their card at a door in an access event, or the cardholder an
operator modified in an operator event. Search for other events related to this example's
cardholder with cardholder=325 in the query parameters.
entryAccessZoneanyThe name and href of the entry access zone related to the event. In the case of card events, it is the zone into which a cardholder was attempting to gain access.
That is true for successful entries, successful exits, and access denials. Regardless of whether the cardholder badged at the entry or exit reader, this field refers to the zone that he or she attempted to access.
For example, for 'Card entry granted' events this field will contain the door's entry zone, but for 'Card exit granted' events this field will contain the door's exit zone, because 'exit granted' means the cardholder was in the door's entry zone, badged at its exit reader, and was granted access to its exit zone. Think of it as going through a door in its reverse direction.
exitAccessZoneanyThe name and href of the exit access zone related to the event. In card events, it is the zone from which a cardholder was attempting to leave, if the door had an exit zone configured (many do not).
That is true for successful entries, successful exits, and access denials. Regardless of whether the cardholder badged at the entry or exit reader, this field refers to the zone that he or she attempted to leave.
For example, for 'Card entry granted' events this field will contain the door's exit zone, if there was one, but for 'Card exit granted' events this field will contain the door's entry zone.
dooranyThe name and href of the door related to the event. These are not as common as you may
think, because when a door is relevant (to card events, for example) it is usually the
event's source, not its door, so it will be in the source block.
New in 8.10.
accessGroupobjectThe href of the access group that a cardholder just gained or lost in a 'Membership Activated' or 'Membership Expired' event. Those happen when a group membership's 'from' or 'until' time passes.
DEPRECATED for the events generated when an operator creates, modifies, or deletes an access group. Use the 'modifiedItem' field instead (new in 8.40).
cardobjectDetails of the card associated with the event.
Versions prior to 8.60 returned this block and a card number of zero for all access events, even if they did not involve a credential (after a person entered their user code at a keypad, for example). Version 8.60 does not return this block for such events.
modifiedItemobjectThe href and type of the item that an operator created, changed, or deleted, for those kinds of events.
New in 8.40.
nextobjectThe URL to the search that will return the page of events following this one. It will include your search filters and pagination parameters.
This link is intended for integrations that take great gulps of events and send them to a downstream system. If it suffers a problem in the middle of a result set, it needs to record where in the event trail it got up to so that when it restarts it will not miss or duplicate events.
New to 8.70.
previousobjectThe URL to the search that will return the page of events preceding this one. It will include your search filters and pagination parameters.
The previous link on the first event in a result set will be the same as the previous
link outside the results, since they both indicate the latest event that preceded the
result set.
New to 8.70.
updatesobjectThe URL to the updates call that will return the page of events following this one, or wait for one to arrive if there are none. It will include your search filters and pagination parameters.
New to 8.70.
lastOccurrenceTimestring<date-time>When the event has occurred multiple times due to flooding, this will show the time it
occurred most recently. It only appears with occurrences, and you will only see it
on the first event in a group.
detailsstringThe full alarm details text. This may be up to 2048 UTF-8 characters, each of which could (theoretically) be four bytes long.
locationobjectAdded in 9.00, this block gathers together information present in about 80 different event types that contain a cardholder's location, presenting it in a consistent way.
It allows a client that is interested in movements to extract what it needs without needing to know what event types to look for or how they represent:
- the cardholder whose location the event contains,
- the access zone that they started in before the event, and
- the access zone or reception where they ended up.
The location block will only be in a result if you ask for it using the fields query
parameter.
{
"href": "https://localhost:8904/api/events/61320",
"id": "61320",
"serverDisplayName": "ruatoria.satellite.int",
"time": "2016-02-18T19:21:52Z",
"message": "Operator logon failed for FT Workstation on GNZ-PC1439",
"occurrences": 2,
"priority": 3,
"alarm": {
"state": "unacknowledged",
"href": "https://localhost:8904/api/alarms/61320"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/325",
"name": "Chong, Marc"
},
"source": {
"id": "321",
"name": "FT Workstation on GNZ-PC1439",
"href": "https://localhost:8904/api/items/321"
},
"group": {
"id": "35",
"name": "Invalid Logon"
},
"type": {
"id": "601",
"name": "Operator logon failed"
},
"eventType": {
"id": "601",
"name": "Operator logon failed"
},
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2",
"name": "Root division"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325",
"id": "325",
"name": "Bruce, Jennifer",
"firstName": "Jennifer",
"lastName": "Caitlin"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Brookwood showroom",
"id": "333"
},
"exitAccessZone": {
"href": "https://localhost:8904/api/access_zones/913",
"name": "Compressor room",
"id": "913"
},
"door": {
"href": "https://localhost:8904/api/doors/745",
"name": "Main hoist door"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"card": {
"facilityCode": "A12345",
"number": "78745",
"issueLevel": 1
},
"modifiedItem": {
"href": "https://localhost:8904/api/cardholders/325",
"type": {
"id": "1",
"name": "Cardholder"
}
},
"next": {
"href": "https://localhost:8904/api/events?pos=61320"
},
"previous": {
"href": "https://localhost:8904/api/events?pos=61320&previous=True"
},
"updates": {
"href": "https://localhost:8904/api/events/updates?pos=61320"
},
"lastOccurrenceTime": "2016-02-18T19:21:59Z",
"details": "Originating IP address: 192.168.2.3",
"location": {
"type": "moved",
"cardholder": {
"name": "Jackson",
"href": "https://localhost:8904/cardholders/325"
},
"beforeLocation": {
"href": "https://localhost:8904/api/access_zones/333",
"name": "Lvl 1 lift lobby",
"canonicalTypeName": "accesszone"
},
"afterLocation": {
"outside": true
}
}
}EventPOSTBody
objecttypeobjectdeprecatedA new event must contain either this or (if your server is running 8.90 or later) eventType.
Without one of them, the POST will fail.
The event type is mandatory because the server cannot assume a reasonable default, as it does for the other fields.
If you send both a type block and an eventType block to a server running 8.90 or later
it will use eventType. Versions before 8.90 do not know about eventType so they use type.
eventTypeanyrequiredA new event must contain either this or type. Without one of them, the POST will
fail.
The event type is mandatory because the server cannot assume a reasonable default, as it does for the other fields.
If you send both a type block and an eventType block to a server running 8.90 or later
it will use eventType. Versions before 8.90 do not know about eventType so they use type.
sourceobjectThis block should contain the href of the item you wish to use as the source of your
event. It can be any site item to which your operator has view access including all
hardware, access zones, fence zones, doors, lockers, car parks, servers, external systems,
and many other item types. If you do not supply one Command Centre will use the REST
Client item identified by the API key in the Authorization header.
Cardholders cannot be event sources. To relate a cardholder to your event, use the
cardholder block.
Make sure that your operator has the 'Create Events and Alarms' privilege on this item's division. 8.90 and later insist on it.
The API will use the source's division as the event's division.
priorityinteger[1, 9]It is not possible to submit an event with priority zero in the body, but if you submit an event with no priority it will use the one on the event type's action plan, which can be zero.
timestring<date-time>Like all other fields in this POST apart from the type, this field is optional. If you send it, it must be in the format described here.
If you do not send this, the server will use the time that it received your request (its "now").
messagestringThis is the first thing an operator will see when they look at this event. Some interactive clients do not give it a lot of room on screen so put the important parts of your message first. It has a limit of 1024 characters.
detailsstringCommand Centre will attach this string to event, as it does the message, but operators will have to look more closely at the event to see it. On the upside, it can be longer than the message: 2048 characters in 8.10.
cardholderobjectIf you wish to attach a cardholder to your event, link it here. Reports can show or filter by the cardholder.
operatorobjectIf you wish to attach an operator to your event, link it here. Like the cardholder, reports can show or filter by the operator.
entryAccessZoneobjectIf you wish to attach an access zone to your event, link it here. Reports can filter by and show the entry access zone on events.
accessGroupobjectIf you wish to attach an access group to your event, link it here.
Unlike cardholders, operators, and entry access zones, access groups do not appear in Command Centre activity reports. You can add a filter to restrict an activity report by access groups, but the group that allowed an event into the report will not appear in a column.
Like all the other items you link to your event it will, of course, appear when you GET the event from the API later.
lockerBankobjectIf you wish to attach a locker bank to your event, link it here.
Like an event's access group, you can filter a Command Centre activity report to events that involve a locker bank, but the bank will not appear in the report itself.
If you link both a locker and a locker bank to an event, Command Centre does not require that the locker is in the locker bank, but you may find that downstream reporting software misbehaves when it is not.
lockerobjectIf you wish to link a locker to your event, do it here. Like an event's access group and locker bank, you can filter a Command Centre activity report to events that involve a locker, but the locker will not appear in the report itself.
doorobjectIf you wish to link a door to your event for later extraction or a report filter, do it here.
{
"type": {
"href": "https://localhost:8904/api/events/types/4000"
},
"eventType": {
"href": "https://localhost:8904/api/events/types/4000"
},
"source": {
"href": "https://localhost:8904/api/doors/745"
},
"priority": 2,
"time": "2019-02-21T14:55:00Z",
"message": "Glass break detected in southwest sauna",
"details": "",
"cardholder": {
"href": "https://localhost:8904/api/cardholders/325"
},
"operator": {
"href": "https://localhost:8904/api/cardholders/5398"
},
"entryAccessZone": {
"href": "https://localhost:8904/api/access_zones/333"
},
"accessGroup": {
"href": "https://localhost:8904/api/access_groups/352"
},
"lockerBank": {
"href": "https://localhost:8904/api/locker_banks/4566"
},
"locker": {
"href": "https://localhost:8904/api/lockers/3456"
},
"door": {
"href": "https://localhost:8904/api/doors/745"
}
}EventGroups
objectCalls to /api/events/groups/ return this object, which is a named array of groups of event types.
eventGroupsArray<object>An array of event group objects. There will be about 150. Most groups contain fewer than 100 event types; one contains around 200.
{
"eventGroups": [
{
"id": "35",
"name": "Invalid Logon",
"eventTypes": [
{
"href": "https://localhost:8904/api/events/types/601",
"id": "601",
"name": "Operator logon failed"
},
{
"href": "https://localhost:8904/api/events/types/20065",
"id": "20065",
"name": "Terminal: Invalid User Code"
},
{
"href": "https://localhost:8904/api/events/types/23052",
"id": "23052",
"name": "Wrong Code only Code"
}
]
}
]
}FenceZoneSearch
objectAn array of fence zone summaries, and a next link for more.
resultsArray<FenceZoneSummary>An array of Fence Zone summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/fence_zones/8487",
"id": "8487",
"name": "Storage yard"
}
],
"next": {
"href": "https://localhost:8904/api/fence_zones?skip=1000"
}
}FenceZoneSummary
object/api/fence_zones returns an array of these. It is a subset of what you get from a
fence zone's detail page at /api/fence_zones/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to a fence zone detail object for this fence zone. This is Command Centre's identifier for this fence zone: use it whenever you need to specify a fence zone in REST operations.
idstringAn alphanumeric identifier, unique to the server.
This is the ID to use in the source parameter of event
filters if you want to limit your events
to those coming from particular fence zones.
namestring{
"href": "https://localhost:8904/api/fence_zones/8487",
"id": "8487",
"name": "Storage yard"
}FenceZoneDetail
object/api/fence_zones/{id} returns one of these.
/api/fence_zones returns an array of these. It is a subset of what you get from a
fence zone's detail page at /api/fence_zones/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to a fence zone detail object for this fence zone. This is Command Centre's identifier for this fence zone: use it whenever you need to specify a fence zone in REST operations.
idstringAn alphanumeric identifier, unique to the server.
This is the ID to use in the source parameter of event
filters if you want to limit your events
to those coming from particular fence zones.
namestringdescriptionstringdivisionobjectThe division containing this Fence Zone.
voltageinteger>= 0The last known voltage on the fence, in volts, to the nearest 100 volts, provided the fence zone is online and the REST server is subscribed to its updates. See the item status section for how to make sure of that.
This value is only up to date when the fence zone is on (shown in the status flags) and has emitted at least one pulse.
Voltages are not displayed by default, because of the caveats around their use. You
must ask for them using the 'fields' parameter: ?fields=voltage,....
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
updatesobjectFollow the URL in the href inside this block to receive the item's current status, then follow
the next link in the results to long poll for changes to that status.
This method only monitors one item at a time. To monitor the status of many items, use the status-monitoring routes.
Update pages take the same fields parameter as summary and details pages. You should use
that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's status
is unknown until something is monitoring it. If you want status flags on the search and
details pages you must ask for them using the fields parameter, but our advice is to monitor
them using status subscriptions if you are running 8.30 or
later, otherwise the item's updates link. See the item status section
for a full description of how to stay up to date with item status, and this item's
introduction in the Operations section for what flags this item might return and what they
mean.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
commandsobjectA block of commands, each represented by a block containing an href that accepts a POST that will send an override to the fence zone, changing its state.
It will be missing if your operator is not privileged to override the fence zone.
See the section 'Creating a Fence Zone' in the Configuration client help for a description of fence zone states.
{
"href": "https://localhost:8904/api/fence_zones/8487",
"id": "8487",
"name": "Storage yard",
"description": "Trailers and pallets.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"voltage": 7300,
"notes": "Multi-line text...",
"shortName": "Short text",
"updates": {
"href": "https://localhost:8904/api/fence_zones/8487/updates/0_0_0"
},
"statusFlags": [
"on",
"highVoltage",
"voltageKnown"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"on": {
"href": "https://localhost:8904/api/fence_zones/8487/on"
},
"off": {
"href": "https://localhost:8904/api/fence_zones/8487/on"
},
"shunt": {
"href": "https://localhost:8904/api/fence_zones/8487/shunt"
},
"unshunt": {
"href": "https://localhost:8904/api/fence_zones/8487/unshunt"
},
"highVoltage": {
"href": "https://localhost:8904/api/fence_zones/8487/high_voltage"
},
"lowFeel": {
"href": "https://localhost:8904/api/fence_zones/8487/low_feel"
},
"cancel": {
"href": "https://localhost:8904/api/fence_zones/8487/cancel"
}
}
}InputSearch
objectAn array of input summaries, and a next link for more.
resultsArray<InputSummary>An array of input summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/inputs/9701",
"id": "9701",
"name": "Studio door open sensor"
}
],
"next": {
"href": "https://localhost:8904/api/inputs?skip=1000"
}
}InputSummary
object/api/inputs returns an array of these. It is a subset of what you get from a
input's detail page at /api/inputs/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to an input detail object for this input.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters if you are only
interested in events from particular inputs.
namestring{
"href": "https://localhost:8904/api/inputs/9701",
"id": "9701",
"name": "Studio door open sensor"
}InputDetail
object/api/inputs/{id} returns one of these.
/api/inputs returns an array of these. It is a subset of what you get from a
input's detail page at /api/inputs/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to an input detail object for this input.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters if you are only
interested in events from particular inputs.
namestringdescriptionstringdivisionobjectThe division containing this input.
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
updatesobjectFollow the URL in the href inside this block to receive the item's current status, then follow
the next link in the results to long poll for changes to that status.
This method only monitors one item at a time. To monitor the status of many items, use the status-monitoring routes.
Update pages take the same fields parameter as summary and details pages. You should use
that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's status
is unknown until something is monitoring it. If you want status flags on the search and
details pages you must ask for them using the fields parameter, but our advice is to monitor
them using status subscriptions if you are running 8.30 or
later, otherwise the item's updates link. See the item status section
for a full description of how to stay up to date with item status, and this item's
introduction in the Operations section for what flags this item might return and what they
mean.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
commandsobjectA block of commands, each represented by a block containing an href that accepts a POST that will send an override to the input.
It will be missing if your operator does not have a privilege that allows overriding the input (examples of which are in the documentation for the POSTs).
{
"href": "https://localhost:8904/api/inputs/9701",
"id": "9701",
"name": "Studio door open sensor",
"description": "Reed switch.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/inputs/9701/updates/0_0_0"
},
"statusFlags": [
"open"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"shunt": {
"href": "https://localhost:8904/api/inputs/9701/shunt"
},
"unshunt": {
"href": "https://localhost:8904/api/inputs/9701/unshunt"
},
"isolate": {
"href": "https://localhost:8904/api/inputs/9701/isolate"
},
"deisolate": {
"href": "https://localhost:8904/api/inputs/9701/deisolate"
}
}
}InterlockGroupSearch
objectAPI support for interlocks is still in development and may change in future versions.
An array of interlock group summaries and a next link for more.
resultsArray<InterlockGroupSummary>An array of interlock group summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/interlock_group/122322",
"name": "Excercise yard egress"
}
],
"next": {
"href": "https://localhost:8904/api/interlock_groups?skip=1000"
}
}InterlockGroupSummary
objectAPI support for interlocks is still in development and may change in future versions.
/api/interlock_groups returns an array of these. It is a subset of what you get from an
interlock's details page at /api/interlock_groups/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to an interlock group detail object for this item.
namestring{
"href": "https://localhost:8904/api/interlock_group/122322",
"name": "Excercise yard egress"
}InterlockGroupDetail
objectAPI support for interlocks is still in development and may change in future versions.
/api/interlock_groups/{id} returns one of these.
Some of the fields will not come from the server unless you request them via the fields
query parameter.
API support for interlocks is still in development and may change in future versions.
/api/interlock_groups returns an array of these. It is a subset of what you get from an
interlock's details page at /api/interlock_groups/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to an interlock group detail object for this item.
namestringidstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters and in the body of status
subscriptions when you are interested in events originating at this item.
descriptionstringdivisionobjectThe division containing this interlock group.
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
updatesobjectFollow the URL in the href inside this block to receive the item's current status, then follow
the next link in the results to long poll for changes to that status.
This method only monitors one item at a time. To monitor the status of many items, use the status-monitoring routes.
Update pages take the same fields parameter as summary and details pages. You should use
that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's status
is unknown until something is monitoring it. If you want status flags on the search and
details pages you must ask for them using the fields parameter, but our advice is to monitor
them using status subscriptions if you are running 8.30 or
later, otherwise the item's updates link. See the item status section
for a full description of how to stay up to date with item status, and this item's
introduction in the Operations section for what flags this item might return and what they
mean.
statusTextstringThis field contains a translated multi-line human-readable description of the item's status. See the statusFlags field for notes on when you should ask for this field and how to keep it up to date.
statusstringThis field contains the statusText field with line endings turned into spaces to make a one-line string. See the statusFlags field for notes on when you should ask for this field and how to keep it up to date.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
commandsobjectA block of commands, each represented by a block containing an href that accepts a POST that will send an override to the interlock.
It will be missing if your operator does not have a privilege that allows overriding the interlock (examples of which are in the documentation for the POSTs).
{
"href": "https://localhost:8904/api/interlock_group/122322",
"name": "Excercise yard egress",
"id": "122322",
"description": "Exercise yard egress.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/interlock_groups/122322/updates/0_0_0"
},
"statusFlags": [
"secure"
],
"statusText": "All doors in the group are in a Secure state.",
"status": "All doors in the group are in a Secure state.",
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"disable": {
"href": "https://localhost:8904/api/interlock_groups/122322/disable"
},
"enable": {
"href": "https://localhost:8904/api/interlock_groups/122322/enable"
}
}
}ItemSearch
objectCalls to /api/items return this object.
resultsArray<ItemSummary>A list of item objects, each containing the alphanumeric ID of the item, its name, and its type.
nextobjectAn href to the next page of results. Missing if there are no more results.
{
"results": [
{
"id": "325",
"name": "Brick, Eva",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
}
},
{
"id": "2707",
"name": "Brewer, Amy",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
}
}
],
"next": {
"href": "https://localhost:8904/api/items?pos=2"
}
}ItemSummary
object/api/items returns an array of these. Because it can contain an item of any type, it only
contains some fields that are common to all item types.
description and shortName arrived in 9.60.
idstringUse this ID in the source filter when requesting events to limit events to those with
that source. Card events such as 'access granted', for example, have a door as their
source.
namestringshortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
descriptionstringIn 9.60 and later.
typeobjectThe type object contains the ID and name of the item's type.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
hrefstringReserved for internal use. Its value changed in version 9.10.
{
"id": "325",
"name": "Brick, Eva",
"shortName": "Short text",
"description": "string",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
},
"serverDisplayName": "ruatoria.satellite.int",
"notes": "Multi-line text...",
"href": "string"
}ItemDetail
objectA call to /api/items/{id} (added in 8.40) returns one of these. It adds the item's division
to the summary object.
/api/items returns an array of these. Because it can contain an item of any type, it only
contains some fields that are common to all item types.
description and shortName arrived in 9.60.
idstringUse this ID in the source filter when requesting events to limit events to those with
that source. Card events such as 'access granted', for example, have a door as their
source.
namestringshortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
descriptionstringIn 9.60 and later.
typeobjectThe type object contains the ID and name of the item's type.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
hrefstringReserved for internal use. Its value changed in version 9.10.
divisionobject{
"id": "325",
"name": "Brick, Eva",
"shortName": "Short text",
"description": "string",
"type": {
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
},
"serverDisplayName": "ruatoria.satellite.int",
"notes": "Multi-line text...",
"href": "string",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
}ItemTypes
objectCalls to /api/items/types return this object, an array of item types.
itemTypesArray<object>A list of item types.
{
"itemTypes": [
{
"id": "1",
"name": "Cardholder",
"canonicalTypeName": "cardholder"
},
{
"id": "2",
"name": "Access Group",
"canonicalTypeName": "accessgroup"
}
]
}ItemUpdate
objectPOSTs and GETs to /api/items/updates return this object. It contains an array of updates
and a next link to collect more.
Added in 8.30.
updatesArray<object>A list of items and their statuses.
On your first two calls—the POST and the first GET—this will contain all the items in your subscription (provided they are the types of item that have statuses). This is unnecessary and incurs a significant performance penalty, so:
Change is coming. A future version will not do that. The results of the POST will not contain all the items in your subscription, and the first GET will not return them all a second time. Use this algorithm.
On subsequent GETs it will only contain the items that received status updates since your previous call. Note that an item will be in this array if it received any status update at all, even if the status flags did not change. Prepare to receive updates that do not contain novel data.
This array is not paginated: it could contain every one of the items you put in your POST.
After about 50 seconds the call will time out and return an empty array here.
nextanyA link to GET more status updates. Do not wait longer than thirty seconds between receiving this link and using it or your subscription will expire and you will need to submit another POST to create a new one.
{
"updates": [
{
"id": "508",
"status": "Controller offline. 62 message(s) pending.",
"statusText": "Controller offline.\n62 message(s) pending.",
"statusFlags": [
"controllerOffline"
]
},
{
"id": "526",
"status": "Disarmed.",
"statusText": "Disarmed.",
"statusFlags": [
"disarmed"
]
},
{
"id": "530",
"status": "This Input is Closed. ",
"statusText": "This Input is Closed. ",
"statusFlags": [
"closed"
]
},
{
"id": "531",
"status": "Awaiting status from Controller.",
"statusText": "Awaiting status from Controller.",
"statusFlags": [
"controllerUnknown"
]
},
{
"id": "532",
"status": "This Output is Off. ",
"statusText": "This Output is Off. ",
"statusFlags": [
"open"
]
},
{
"id": "533",
"status": "Secure.",
"statusText": "Secure.",
"statusFlags": [
"secure"
]
}
],
"next": {
"href": "https://localhost:8904/api/items/updates?bookmark=3ec613a1-de01c6e_0"
}
}LockerBankSearch
objectAn array of locker bank summaries, and a next link for more.
resultsArray<LockerBankSummary>An array of locker summaries.
nextobjectThe link to the next page of results. Absent if you have retrieved them all.
{
"results": [
{
"name": "Lobby",
"href": "https://localhost:8904/api/locker_banks/4566",
"description": "Behind reception",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
},
{
"name": "Bank A",
"href": "https://localhost:8904/api/locker_banks/4567",
"description": "Level 4 east A",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
}
],
"next": {
"href": "https://localhost:8904/api/locker_banks?skip=2"
}
}LockerBankSummary
objectThe locker bank search at /api/locker_banks returns an array of these. It is a subset of
what you get from a locker bank's detail page at /api/locker_banks/{id} (linked as the href
in this object).
Like the cardholder and access group summary pages in this API, this contains only the basic information about a locker bank. It is the result of a search and could return many items, and we did not want the size getting out of hand.
Also like the other summary pages in this API, you can add all the fields you want using the
fields query parameter to the API routes that return it.
The most important field is the href to the detail page, covered next.
hrefstring<uri-reference>A link to the detail page for this locker bank.
namestringshortNamestringThe bank's short name.
descriptionstringdivisionobjectThe division containing this locker bank.
{
"href": "https://localhost:8904/api/locker_banks/4566",
"name": "Lobby, building 28",
"shortName": "Lobby",
"description": "Behind reception",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
}
}LockerBankDetail
object/api/locker_banks/{id} returns one of these. It contains an array of all the lockers in the bank, each of which contains an array of the cardholders assigned to that locker.
This example shows two lockers on the bank, called 'L1' and 'L2'. L1 has two cardholders who can open it: one for two years, the other for a week in April 2018.
hrefstring<uri-reference>A self-reference.
namestringshortNamestringdescriptionstringdivisionobjectThe division containing this locker bank.
notesstringconnectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
lockersArray<object>An array of locker objects, each containing the details of the locker and the cardholders who can open it.
{
"name": "Lobby",
"href": "https://localhost:8904/api/locker_banks/4566",
"description": "Behind reception",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"lockers": [
{
"name": "Lobby locker 1",
"shortName": "L1",
"description": "Wheelchair-suitable",
"href": "https://localhost:8904/api/lockers/3456",
"assignments": [
{
"href": "https://localhost:8904/api/cardholders/325/lockers/abe3456e",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://localhost:8904/api/cardholders/325"
},
"from": "2018-01-01T00:00:00Z",
"until": "2020-01-01T00:00:00Z"
},
{
"href": "https://localhost:8904/api/cardholders/10135/lockers/deb9456f",
"cardholder": {
"name": "Messervy, Miles",
"href": "https://localhost:8904/api/cardholders/10135"
},
"from": "2018-04-01T05:00:00Z",
"until": "2018-04-07T00:00:00Z"
}
],
"commands": {
"open": {
"href": "https://localhost:8904/api/lockers/3456/open"
},
"quarantine": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"quarantineUntil": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"cancelQuarantine": {
"href": "https://localhost:8904/api/lockers/3456/cancel_quarantine"
}
}
},
{
"name": "Lobby locker 2",
"shortName": "L2",
"description": "Faulty USB charging port",
"href": "https://localhost:8904/api/lockers/3457",
"assignments": [
{
"cardholder": {
"name": "R",
"href": "https://localhost:8904/api/cardholders/10136"
},
"from": "1999-11-08T00:00:00Z",
"until": "2002-11-20T00:00:00Z"
}
],
"commands": {
"open": {
"href": "https://localhost:8904/api/lockers/3457/open"
},
"quarantine": {
"href": "https://localhost:8904/api/lockers/3457/quarantine"
},
"quarantineUntil": {
"href": "https://localhost:8904/api/lockers/3457/quarantine"
},
"cancelQuarantine": {
"href": "https://localhost:8904/api/lockers/3457/cancel_quarantine"
}
}
}
]
}LockerDetail
object/api/lockers/{id} returns one of these. It contains some basic data about a locker, its assignments (the cardholders who can open it), and a link to override it open.
hrefstring<uri-reference>A self-reference.
namestringshortNamestringdescriptionstringdivisionobjectThe division containing this locker.
notesstringThe notes field is not in the default result set. You must ask for it using fields.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
assignmentsArray<object>A locker can have many assignments. Each contains a cardholder and up to two dates, between which the cardholder can open the locker. If either of the dates is missing, the assignment is unbounded in that direction.
commandsobjectOverrideable items return one of these blocks if your operator has the right privilege and the locker is fit to be overridden. It is a list of commands, each represented by a block containing an href that accepts a POST that will send an override to the locker, changing its state.
updatesobjectFollow the URL in the href inside this block to receive the locker's current status, then
follow the next link in the results to long poll for changes to that status.
updates was added to the lockers controller in 9.10 for consistency with other
controllers, but we discourage its use in favour of bulk status
monitoring.
Although a locker's status includes 'free' or 'allocated', allocating or un-allocating a locker does not cause the API to report a change of status. Opening, closing, quarantining, and unquarantining a locker do.
{
"href": "https://localhost:8904/api/lockers/3456",
"name": "Lobby locker 1",
"shortName": "L1",
"description": "Wheelchair-suitable",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "string",
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"assignments": [
{
"href": "https://localhost:8904/api/cardholders/325/lockers/abe3456e",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://localhost:8904/api/cardholders/325"
},
"from": "2018-01-01T00:00:00Z",
"until": "2020-01-01T00:00:00Z"
},
{
"href": "https://localhost:8904/api/cardholders/10135/lockers/deb9456f",
"cardholder": {
"name": "Messervy, Miles",
"href": "https://localhost:8904/api/cardholders/10135"
},
"from": "2018-04-01T05:00:00Z",
"until": "2018-04-07T00:00:00Z"
}
],
"commands": {
"open": {
"href": "https://localhost:8904/api/lockers/3456/open"
},
"quarantine": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"quarantineUntil": {
"href": "https://localhost:8904/api/lockers/3456/quarantine"
},
"cancelQuarantine": {
"href": "https://localhost:8904/api/lockers/3456/cancel_quarantine"
}
},
"updates": {
"href": "https://localhost:8904/api/lockers/3456/updates"
}
}MacroSearch
objectAn array of macro summaries, and a next link for more.
resultsArray<MacroSummary>An array of macro summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/macros/8492",
"id": "8492",
"name": "Arm lobby"
}
],
"next": {
"href": "https://localhost:8904/api/macros?skip=1000"
}
}MacroSummary
object/api/macros returns an array of these. It is a subset of what you get from a
macro's detail page at /api/macros/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to a macro detail object for this macro.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters if you are only
interested in events from particular macros (which would be unusual, since macros do not
generate many events).
namestring{
"href": "https://localhost:8904/api/macros/8492",
"id": "8492",
"name": "Arm lobby"
}MacroDetail
object/api/macros/{id} returns one of these.
It contains a block called updates. This is reserved for future development and its
behaviour will change in later versions of Command Centre.
/api/macros returns an array of these. It is a subset of what you get from a
macro's detail page at /api/macros/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to a macro detail object for this macro.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters if you are only
interested in events from particular macros (which would be unusual, since macros do not
generate many events).
namestringdescriptionstringdivisionobjectThe division containing this Macro.
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
commandsobjectThe only thing you can do to a macro via REST is run it, so this block only contains one command, and only if your operator is privileged to do that.
{
"href": "https://localhost:8904/api/macros/8492",
"id": "8492",
"name": "Arm lobby",
"description": "Arms and secures all lobby zones.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"commands": {
"run": {
"href": "https://localhost:8904/api/macros/8492/run"
}
}
}OperatorGroupSearch
objectAn array of operator group summaries, described in the next section, and a next link for
more.
resultsArray<OperatorGroupSummary>An array of operator group summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/operator_groups/523",
"name": "Locker admins.",
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://localhost:8904/api/operator_groups?skip=61320"
}
}OperatorGroupSummary
objectThe operator group search at /api/operator_groups returns an array of these, and
/api/operator_groups/{id} (linked as the href in this object) returns one with more fields.
hrefstring<uri-reference>A link to an operator group detail object for this operator group.
namestringserverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
{
"href": "https://localhost:8904/api/operator_groups/523",
"name": "Locker admins.",
"serverDisplayName": "ruatoria.satellite.int"
}OperatorGroupDetail
object/api/operator_groups/{id} returns one of these. In addition to the basic item details such as division and description, it lists the divisions in which it grants privileges to its members.
The operator group search at /api/operator_groups returns an array of these, and
/api/operator_groups/{id} (linked as the href in this object) returns one with more fields.
hrefstring<uri-reference>A link to an operator group detail object for this operator group.
namestringserverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division that contains this operator group. This has no bearing on the divisions
to which this operator group grants privileges: that is divisions.
cardholdersobjectFollowing this link lists the group's cardholder members.
divisionsArray<object>An array containing the divisions in which this operator group grants its privileges.
{
"href": "https://localhost:8904/api/operator_groups/523",
"name": "Locker admins.",
"serverDisplayName": "ruatoria.satellite.int",
"description": "For managing locker assignments.",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"cardholders": {
"href": "https://localhost:8904/api/operator_groups/523/cardholders"
},
"divisions": [
{
"division": {
"name": "Staff",
"href": "https://localhost:8904/api/divisions/647"
}
},
{
"division": {
"name": "Contractors",
"href": "https://localhost:8904/api/divisions/649"
}
}
]
}OperatorGroupMembership
objectReturned in an array by /api/operator_groups/{id}/cardholders, containing cardholders who are members of a particular operator group.
Each item contains the cardholder's name and (if your operator has the privilege to view that cardholder) an href to the cardholder record. 8.70 added an href that you can use to delete the operator group membership.
PATCH the href in the cardholder block if you want to change anything about that cardholder. The operator groups they are in, for example.
hrefstring<uri-reference>DELETE this URL to remove the cardholder from this operator group.
DELETE is the only verb you can use on this URL. GET will return a 404.
It is not a default field. If you want it, you need to request it using the fields
query parameter.
Added in 8.70.
cardholderobjectThe name and href of the member cardholder.
{
"href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD",
"cardholder": {
"name": "Boothroyd, Algernon",
"href": "https://localhost:8904/api/cardholders/325"
}
}OutputSearch
objectAn array of output summaries, and a next link for more.
resultsArray<OutputSummary>An array of output summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/outputs/2365",
"id": "2365",
"name": "Studio door red/green"
}
],
"next": {
"href": "https://localhost:8904/api/outputs?skip=1000"
}
}OutputSummary
object/api/outputs returns an array of these. It is a subset of what you get from a
output's detail page at /api/outputs/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to an output detail object for this output.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters if you are only
interested in events from particular outputs.
namestring{
"href": "https://localhost:8904/api/outputs/2365",
"id": "2365",
"name": "Studio door red/green"
}OutputDetail
object/api/outputs/{id} returns one of these.
/api/outputs returns an array of these. It is a subset of what you get from a
output's detail page at /api/outputs/{id} (linked as the href in this object).
hrefstring<uri-reference>A link to an output detail object for this output.
idstringAn alphanumeric identifier, unique to the server. This is the ID to use in the source
parameter of event filters if you are only
interested in events from particular outputs.
namestringdescriptionstringdivisionobjectThe division containing this output.
shortNamestringShort names are not displayed by default. You must ask for them using the 'fields' parameter:
?fields=shortname,....
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
updatesobjectFollow the URL in the href inside this block to receive the item's current status, then follow
the next link in the results to long poll for changes to that status.
This method only monitors one item at a time. To monitor the status of many items, use the status-monitoring routes.
Update pages take the same fields parameter as summary and details pages. You should use
that to request all the fields you need in the update.
statusFlagsArray<string>The search and details pages do not return status flags by default, because an item's status
is unknown until something is monitoring it. If you want status flags on the search and
details pages you must ask for them using the fields parameter, but our advice is to monitor
them using status subscriptions if you are running 8.30 or
later, otherwise the item's updates link. See the item status section
for a full description of how to stay up to date with item status, and this item's
introduction in the Operations section for what flags this item might return and what they
mean.
connectedControllerobjectThis block describes this item's hardware controller.
Retrieving it takes a little more time than the other fields so only ask for it if you need it.
Added in 8.50.
commandsobjectA block of commands keyed by a fixed enumeration given below. The values are blocks containing an href that accepts a POST that will send an override to the output, turning it on or off.
It will be missing if your operator does not have a privilege that allows overriding the output (examples of which are in the documentation for the POSTs).
{
"href": "https://localhost:8904/api/outputs/2365",
"id": "2365",
"name": "Studio door red/green",
"description": "Red or green, controlled from sound desk.",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"shortName": "Short text",
"notes": "Multi-line text...",
"updates": {
"href": "https://localhost:8904/api/outputs/2365/updates/0_0_0"
},
"statusFlags": [
"open",
"overridden"
],
"connectedController": {
"name": "Fourth floor C7000",
"href": "https://localhost:8904/api/items/508",
"id": "634"
},
"commands": {
"on": {
"href": "https://localhost:8904/api/outputs/2365/on"
},
"onUntil": {
"href": "https://localhost:8904/api/outputs/2365/on"
},
"off": {
"href": "https://localhost:8904/api/outputs/2365/off"
},
"offUntil": {
"href": "https://localhost:8904/api/outputs/2365/off"
},
"pulse": {
"href": "https://localhost:8904/api/outputs/2365/pulse"
}
}
}PDFDefinitionSearch
objectAn array of PDF definition summaries, and a next link for more.
resultsArray<PDFDefinition>An array of PDF definition summaries.
nextobjectThe link to the next page of results. Absent if you have retrieved them all.
{
"results": [
{
"name": "email",
"id": "5516"
},
{
"name": "cellphone",
"id": "9998",
"serverDisplayName": "ruatoria.satellite.int"
}
],
"next": {
"href": "https://localhost:8904/api/personal_data_fields?pos=900&sort=id"
}
}PDFDefinition
object/api/personal_data_fields returns an array of these. By default it gives you just the
basics about a PDF: its ID, href, name, and (if it is remote) the name of its home server.
By using the fields parameter you can add more.
idstringread onlyAn alphanumeric identifier, unique to the server. Use it to filter cardholder searches and to add your external ID to the results of an event search.
namestringserverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division containing this PDF definition. Required when creating one.
typestringstringimagestrEnumnumericdateaddressphoneemailmobileThe type of PDF: string, image, email address, etc.
Required when creating a new PDF definition, but ignored when PATCHing one since a PDF's type is fixed once set.
defaultstringThis is the value that new cardholders will receive, if not supplied by the creating client. Required for 'required' PDFs.
This field will be missing if there is no default value.
To clear the default, send the blank string "".
requiredbooleanfalseIf true, every cardholder with this PDF must have a value for it. No blanks allowed.
uniquebooleanfalseIf true, every cardholder with a value for this PDF must have a different value.
After 8.70 this will not show for date and image PDFs, because it can never be true for them.
defaultAccessstringnoAccessreadOnlyfullAccessfullAccessThis is the access that operators will have to cardholders' values of this PDF if the operator is not a member of an operator group that overrides it. Check the operator group's 'Personal data' tab in the Configuration Client.
operatorAccessstringnoAccessreadOnlyfullAccessread onlyThis is the access that your operator has to cardholders' values of this PDF including the
permissions granted by this cardholder's operator groups. You will only
get this field if you ask for it with the fields parameter.
New in 8.80.
sortPriorityintegerThis is called 'sort order' in the Configuration Client. Interactive clients use this number to order the list of PDFs on a cardholder. It has no effect on access control or this API.
accessGroupsArray<object>read onlyThis array contains a block for each access group that gives this PDF to its members. Each block contains the group's name, and if the operator has read access to the group, its href.
notificationDefaultbooleanThis value is copied to the 'notification' flag on a cardholder's value for this PDF when
they first gain membership of one of this PDF's access groups. It will only appear for
PDF types that can receive notifications (email addresses and mobile numbers), and only if
you ask for it with the fields parameter. The server will ignore it if you send it in a
POST or PATCH to a PDF that is not email or mobile.
New in 8.50.
regexstringThis is the regular expression that a string-valued PDF value must match before Command Centre will accept it on a cardholder.
To remove the requirement that PDF values match a regular expression, send the empty
string "".
regexDescriptionstringRegular expressions often need explaining.
imageWidthintegerThe maximum width of an image stored in this PDF, in pixels, for image PDF types. You
will only get this field if you ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.50.
imageHeightintegerThe maximum height of an image stored in this PDF, in pixels, for image PDF types. You
will only get this field if you ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.50.
imageFormatstringbmpjpgpngdeprecatedWhether this image PDF stores BMPs, JPEGs, or PNGs. You will only get this field if you
ask for it with the fields parameter.
New in 8.50. Deprecated in 8.70 by contentType, which is more standard.
contentTypestringimage/bmpimage/jpegimage/pngWhether this image PDF stores BMPs, JPEGs, or PNGs. You will only get this field if you
ask for it with the fields parameter.
You can set this on a new PDF definition but not change it on an existing definition.
New in 8.70.
isProfileImagebooleanTrue if and only if this PDF holds images and it is set as a profile image.
Unlike many other properties of an image PDF, this can be changed at will.
New in 8.70.
{
"id": "string",
"name": "email",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Corporate mailbox",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"type": "email",
"default": "contact@example.com",
"required": false,
"unique": false,
"defaultAccess": "fullAccess",
"operatorAccess": "fullAccess",
"sortPriority": 50,
"accessGroups": [
{
"name": "All Staff"
},
{
"name": "R&D Special Projects Group",
"href": "https://localhost:8904/api/access_groups/352"
}
],
"notificationDefault": false,
"regex": ".*@.*",
"regexDescription": "@ least",
"imageWidth": 600,
"imageHeight": 800,
"imageFormat": "jpg",
"contentType": "image/jpeg",
"isProfileImage": false
}CHUID
objectThis is the model of a CHUID block for a PIV card. The only difference between this and a PIV-I card is the FASC-N.
hashstringrequiredThis is the hash of the CHUID object, Base64-encoded. For a 256-bit hash it
should be 44 characters long including one = pad. The API will reject a string that is
not valid Base64, but it will not verify the hash.
Required when creating a PIV card in versions up to 8.60. Optional in 8.70 and later.
fascnstringrequiredThis is the FASC-N identifier. You must supply it when creating a card.
It must be the same as the card number on a PIV (not PIV-I) card.
On a PIV-I card, it must not be the same as the card number (and for Federal PIV-I cards it will likely begin with fourteen nines).
orgIdentifierstringOptional.
dunsstringOptional.
{
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
}NewPIVCard
objectA new PIV card. You would put this in the cards array of a cardholder you were creating with a POST,
or the cards.add array of a cardholder you were modifying with a PATCH.
typeobjectrequiredstatusobjectnumberstringrequiredWhile the card number rules on a PIV or PIV-I card type are different from those on other types, card numbers are still strings, and the API will accept them from you and return them to you in the same way.
PIV card numbers are the FASC-N with hyphens splitting the major components.
PIV-I card numbers are the CHUID's GUID in decimal.
pivDataobjectrequiredThese are the PIV-specific fields that you can set on a new card. They
are the same as you [receive from the API](#path-PIV-cards/get/api/cardholders/{id} [PIV]) for
an existing PIV card except that you do not send a lastCheckTime.
{
"type": {
"href": "string"
},
"status": {
"value": "string"
},
"number": "3165-4313-245789-098765432113456799",
"pivData": {
"chuid": {
"hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
"fascn": "47000256001337111234567890199991",
"orgIdentifier": "",
"duns": ""
},
"pivStatus": {
"type": "Normal"
},
"contentSigningCert": "MIIE[...]Kltk=",
"cardAuthenticationCert": "MIIE[...]e5mE=",
"pivAuthenticationCert": "MIIE[...]wkrp",
"fingerprints": "N7[...]Shpd="
}
}ReceptionSearch
objectAn array of receptions, and a next link for more.
resultsArray<Reception>An array of receptions.
nextobjectThe link to the next page of results. Absent if you have retrieved them all.
{
"results": [
{
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937"
},
{
"name": "Green Dragon main desk",
"href": "https://localhost:8904/api/receptions/979"
}
],
"next": {
"href": "https://localhost:8904/api/receptions?pos=1000"
}
}Reception
object/api/receptions returns an array of these, and /api/receptions/{id} returns one. Each
gives you enough about a reception to identify it and use it in a visit: its href, name, and
(if you ask for them using the fields parameter) its description, default visitor type, and
notes.
namestringhrefstring<uri-reference>This is the href to use when creating a visit.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringShort free-form text. Searches do not return an item's description - ask for it using the
fields parameter.
divisionobjectThe division containing this reception.
defaultVisitorTypeobjectGallagher's visitor management applications use this to pre-fill a UI element prompting the user to pick a visitor type when they are creating a visit for this reception. The server does not use it.
notesstringFree-form text. You will only get this field if you ask for it with the fields
parameter.
{
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Security foyer in B1",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"defaultVisitorType": {
"href": "https://localhost:8904/api/divisions/2/v_t/925",
"accessGroup": {
"name": "Visitor group 1",
"href": "https://localhost:8904/api/access_groups/925"
}
},
"notes": "string"
}Redaction
objectAn array of these comes from GET /cardholders/redactions.
These appear in an array in a cardholder object. Each describes a redaction scheduled for the cardholder.
A cardholder can have multiple event redactions pending, because they can operate on events from different periods, but since a cardholder redaction removes the cardholder item there can be only one.
hrefstring<uri-reference>DELETE this URL to cancel this redaction.
DELETE is the only verb you can use on this URL. GET will always return a 404.
typestringnormalEventscardholderWhether this redaction is for cardholder events or cardholder information.
whenstring<date-time>When redaction is meant to happen. This should be in the future. If it is in the past, the service returns 400-Bad Request Invalid Start Time.
Optional. If it is absent, it means to do it asap.
beforestring<date-time>For event redactions, do not redact any events after this time. No effect on cardholder information redactions.
Optional.
statusstringpendinginProgresscancelleddonefailedThe status of this redaction.
redactionOperatorobjectA block containing the href and current name of the operator who scheduled the redaction.
cardholderobjectThe href of the cardholder whose events or item this redaction should affect.
Required.
finishedstring<date-time>When the redaction finished.
Will be missing from pending redactions.
messagestringTranslated string from the redaction's error code. Will be absent if empty, or if the redaction is pending or complete.
detailsstringA more detailed description of what went wrong.
Not translated. Will be absent if empty, or if the redaction is pending or complete.
{
"href": "https://localhost:8904/api/cardholders/redactions/625",
"type": "normalEvents",
"when": "2023-01-01T00:00:00Z",
"before": "2022-01-01T00:00:00Z",
"status": "pending",
"redactionOperator": {
"name": "REST Operator",
"href": "https://localhost:8904/api/items/100"
},
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"finished": "2022-01-01T00:00:00Z",
"message": "Invalid cardholder",
"details": ""
}RedactionPOST
objectPOST one of these to schedule a redaction.
These fields are common to redaction POSTs and GETs.
cardholderobjectThe href of the cardholder whose events or item this redaction should affect.
Required.
typestringnormalEventscardholderWhether this redaction is for events or cardholder information.
Required.
beforestring<date-time>For event redactions, do not redact any events after this time. No effect on cardholder information redactions.
Optional.
whenstring<date-time>When redaction is meant to happen. This should be in the future. If it is in the past, the service returns a 400.
Optional. If it is absent, it means to do it asap.
{
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z",
"when": "2023-01-01T00:00:00Z"
}RedactionCommonFields
objectThese fields are common to redaction POSTs and GETs.
cardholderobjectThe href of the cardholder whose events or item this redaction should affect.
Required.
typestringnormalEventscardholderWhether this redaction is for events or cardholder information.
Required.
beforestring<date-time>For event redactions, do not redact any events after this time. No effect on cardholder information redactions.
Optional.
{
"cardholder": {
"href": "https://localhost:8904/api/cardholders/630"
},
"type": "normalEvents",
"before": "2022-01-01T00:00:00Z"
}Role
object/api/roles returns an array of these. Each element gives you enough about a role to
identify it and use it in a cardholder PATCH: its href, name, description, and (if you ask
for them using the fields parameter) notes.
In 9.10 or later you can send one of these in a POST to create a new role, or in a PATCH to modify an existing one.
namestringhrefstring<uri-reference>read onlyThis is the string to use when creating a relationship between cardholders using this role.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringdivisionobjectThe division containing this role. Required in a POST, optional in a PATCH.
enableCompetencyExpiryWarningsbooleanControls whether CC sends notifications to a person holding this role when their staff's competencies are about to expire.
enableCardExpiryWarningsbooleanControls whether CC sends notifications to a person holding this role when their staff's cards are about to expire.
{
"name": "Supervisor",
"href": "https://localhost:8904/api/roles/1399",
"serverDisplayName": "ruatoria.satellite.int",
"description": "aka floor manager",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"enableCompetencyExpiryWarnings": false,
"enableCardExpiryWarnings": true
}ScheduleSearch
objectAn array of schedule summaries, and a next link for more.
resultsArray<ScheduleSummary>An array of schedule summaries.
nextobjectThe link to the next page. Absent if you have retrieved them all.
{
"results": [
{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure"
}
],
"next": {
"href": "https://localhost:8904/api/schedules?skip=1000"
}
}ScheduleSummary
objectA schedule search returns an array of these. It is a
subset of what you get from a detail page at /api/schedules/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to a schedule detail.
namestring{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure"
}ScheduleDetail
object/api/schedules/{id} returns one of these, and you put one of these in the body of a POST or PATCH to create or edit a schedule.
A schedule search returns an array of these. It is a
subset of what you get from a detail page at /api/schedules/{id} (linked as the href in this
object).
hrefstring<uri-reference>A link to a schedule detail.
namestringdescriptionstringdivisionobjectThe division containing this schedule. It is possible to change a schedule's division after creating it.
notesstringBecause of their potential size, notes are only available by request. Use the 'fields' parameter:
?fields=defaults,notes,...
typeobjectThis block contains a string field type which will (in the server's response to a
GET) or must (in the body of your POST) be one of the seven schedule types. It is not a
valid field in a PATCH, because you cannot change the type of an existing schedule.
Elevator kiosk control schedules arrived in 8.60.
dayCategoriesArray<object>This is the part of the schedule that controls when changes occur on its scheduled items.
It is an array of objects, each containing a day category and an array of times. The
day category picks days of the year, and the times array sets what will happen and
at what times on those days.
In the body of your POST:
The
timefield of each object insidetimesmust be a string of the formHH:MMbetween 00:00 and 23:59. If you find another format that works (four zeroes, for example), it may not work in future versions.Each day category must have an entry at 00:00. This means an item does not need to search back in time to find what state it should be in when it first comes online.
Each day category cannot have more than one entry at the same time.
If you receive a 400 response to your POST or PATCH and one of those rules is the cause, the body of the response should contain a reminder.
The state field is an array describing what should happen at that time. It is
comparable to the statusFlags arrays on access zones, alarm zones, outputs, and
fence zones. For most schedule types it will contain only one word, but the extra
flag usePin may accompany access zone state changes.
When building your JSON, treat these string comparisons as case-sensitive.
All schedule types can have a state change called 'cancelUntimedOverrides'. That sets an item back to its scheduled state if it was under the effect of an override with no end time.
Other than that, each schedule type has its own set of state changes:
An Access Schedule, also known as a Cardholder Access Schedule, controls the ability of the members of an access group to pass into an access zone. The valid states are
grantanddeny. Note that 'deny' is a misnomer: a cardholder will gain access through a door if they are a member of a different access group that still has access.An Access Zone Schedule controls the mode of an access zone. The valid states are the same as the zone's status flags:
secure,dualAuth,codeOrCard, orfree. The extra flagusePinmeans the same here as it does in the status flags: people will need their PINs at readers and alarms terminals.An Alarm Zone Schedule switches an alarm zone between its four modes:
set,unset,user, anduser2. Use the wordsuser1anduser2here even if you have renamed them in the server properties.An Output Schedule can be
onoroff, plain and simple.A Notification Schedule controls when notifications go out. They can make sure that notifications go to the people who are on shift, and they can prevent bothering people at night. Like the access and output schedules it is binary, but the valid states are called
notificationEnabledandnotificationDisabled.A HV/LF Schedule controls the voltage on an electric fence. 'lowFeel' mode allows detection without the deterrant of
highVoltate.
This example sets an access zone to secure mode between 7.30am and 6:00pm on work days, and secure plus PIN mode at all other times.
scheduledItemsArray<object>read onlyAn array containing the names and hrefs of all the items that this schedule controls and that the operator has the privilege to view.
This is generated data. A schedule resides in the configuration of the items that use it, rather than the other way around, so this block is a handy aggregation of the inverse of those relationships. As such it is read-only: the server will ignore it if you send it in a POST or a PATCH.
{
"href": "https://localhost:8904/api/schedules/6",
"name": "Default Access Zone Secure",
"description": "Secure 24/7",
"division": {
"href": "https://localhost:8904/api/divisions/2"
},
"notes": "Multi-line text...",
"type": {
"type": "accessZoneSchedule"
},
"dayCategories": [
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/3",
"name": "Default Day Category"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
},
{
"time": "07:30",
"state": [
"secure"
]
},
{
"time": "18:00",
"state": [
"secure",
"usePin"
]
}
]
},
{
"dayCategory": {
"href": "https://localhost:8904/api/day_categories/300",
"name": "Weekends and holidays"
},
"times": [
{
"time": "00:00",
"state": [
"secure",
"usePin"
]
}
]
}
],
"scheduledItems": [
{
"href": "https://localhost:8904/api/items/637",
"name": "Access Zone 1"
},
{
"href": "https://localhost:8904/api/items/638",
"name": "Access Zone 2"
}
]
}CardholderRedaction
objectThese appear in an array in a cardholder object. Each describes a redaction scheduled for the cardholder.
A cardholder can have multiple event redactions pending, because they can operate on events from different periods, but since a cardholder redaction removes the cardholder item there can be only one.
hrefstring<uri-reference>DELETE this URL to cancel this redaction.
DELETE is the only verb you can use on this URL. GET will always return a 404.
typestringnormalEventscardholderWhether this redaction is for cardholder events or cardholder information.
whenstring<date-time>When redaction is meant to happen. This should be in the future. If it is in the past, the service returns 400-Bad Request Invalid Start Time.
Optional. If it is absent, it means to do it asap.
beforestring<date-time>For event redactions, do not redact any events after this time. No effect on cardholder information redactions.
Optional.
statusstringpendinginProgresscancelleddonefailedThe status of this redaction.
redactionOperatorobjectA block containing the href and current name of the operator who scheduled the redaction.
{
"href": "https://localhost:8904/api/cardholders/redactions/625",
"type": "normalEvents",
"when": "2023-01-01T00:00:00Z",
"before": "2022-01-01T00:00:00Z",
"status": "pending",
"redactionOperator": {
"name": "REST Operator",
"href": "https://localhost:8904/api/items/100"
}
}Visit
objectGET /api/visits returns an array of these (with fewer fields) and GET /api/visits/{id} returns one.
namestringYou can name a visit however you like. Gallagher's in-house applications name them after their hosts.
This field is mandatory when creating a visit. No blank names allowed!
hrefstring<uri-reference>This is the URL to send a PATCH to when modifying an existing visit. You can find it in the body of a GET that returns a visit, or in the Location header of a POST that creates one.
It is not necessary in a POST or PATCH. The server will ignore it if you send it.
serverDisplayNamestringread onlyIf you are running a multi-server installation and this item is homed on a remote server, this field will contain the name of that server. This field is missing from items that are held on the machine that served the API request. Added in 8.40.
This is a read-only field. The server will ignore it if you send it.
descriptionstringA visit's description is free text. Gallagher's visitor management clients use it to store the visit's purpose. You can change it at any time.
divisionobjectThe division containing this visit.
It will be the visit's reception's division, or if that division did not have an active visitor management configuration when the visit was created, the first ancestor up the division tree that did.
The server will ignore it if you send it, because you cannot change a visit's division directly. You can affect it indirectly by changing the visit's reception.
receptionobjectEvery visit must have a reception. It represents the location at which your visitors will first arrive. Having one attached to a visit allows a kiosk at that location to hide visits for other receptions, revealing only those meant to start there.
Your choice of reception also determines the rules with which the visit must comply, because those rules come from the visitor management configuration on the reception's division.
Since every visit must have a reception, every POST that creates a visit must have one. Once you have a visit in the system you can change its reception, but be aware that changing receptions might change the visit's division, which also might change the rules to which the visit must conform, and if any validation fails--and there is a lot of it--the PATCH will fail.
The reception's name comes from the server in a GET, but you need not send it when creating or modifying a visit. Command Centre only needs an href from you.
Note that you must send the reception like it appears in the example, as an href inside a
block called reception. This will not work:
"reception": "https://localhost:8904/api/receptions/123"
visitorTypeobjectEvery visit must also have a visitor type. It provides an access group for your visitors so that they can have personal data (remember that a cardholder must be in an access group before he or she can have a PDF), and it is an index into the visitor management configuration that governs things like the host (the cardholder who is responsible for your visitors) and the access groups that the server will give your visitors when they sign in.
Use the href from the division's visitor management configuration, or the access group's href. Both will work.
You will receive the accessGroup object inside the visitorType block in a GET, but you
needn't send it in a POST or PATCH. The server can find the visitor type using only its href.
Like the reception, when you send this to the server you must use the same form as the
example, as an href inside a block called visitorType. The href can be to an access group
that you got from an access group or the
visitorTypes in the visitor management configuration on a
division. For example, both of these will
work as visitor type hrefs:
https://localhost:8904/api/access_groups/925 (taken from another visit, or a search of
access groups)
https://localhost:8904/api/divisions/2/visitor_types/925 (taken from a division config)
Your operator must have the privilege to view the access group, otherwise you will be told 'Access denied when writing to one or more fields'. Having the 'Modify Access Control' privilege on the group's division is a good choice because it not only gives you a view of access groups but the ability to change cardholders' membership of them, which you need to add visitors and visitor groups.
hostobjectA host is a cardholder, and every visit has one. A visit's host is the person who (optionally) receives an email or SMS notification when visitors start signing in, and is responsible for them until they sign out again.
A host must be a member of at least one of the visit's visitor type's host access groups
at the time that you create or modify the visit. To find out what those are, get the
visitor management configuration for your reception's division (remembering that your
visit's division is its reception's division). That visitor type contains an array of
access groups called hostAccessGroups. Your host cardholder must be a member of one of
those groups or their descendants.
When you GET a visit the server will send you the host cardholder's name, but when you create a visit with a POST or modify one with a PATCH, you needn't send the name back. Do so if you must, but it will not have an effect.
fromstring<date-time>Every visit has a validity period, defined by start and end times called from and
until.
Not only are they both required when you create a visit, but the server will insist that
until is later than from and that you followed the date-time rules.
You can use short forms such as 2021-04-01+12 and the server will fill in zeroes for the
parts you missed. But because the end time must always be later than the start time you
cannot use the same date for both start and end. The server will treat them both like
midnight, see that they are equal, and reject your API call. So if you have a one-day visit,
bump until by a day or pad it out until the evening. 2021-04-01T23:59:59+12, for example.
untilstring<date-time>Every visit has a validity period, defined by start and end times called from and
until.
Not only are they both required when you create a visit, but the server will insist that
until is later than from and that you followed the date-time rules.
You can use short forms such as 2021-04-01+12 and the server will fill in zeroes for the
parts you missed. But because the end time must always be later than the start time you
cannot use the same date for both start and end. The server will treat them both like
midnight, see that they are equal, and reject your API call. So if you have a one-day visit,
bump until by a day or pad it out until the evening. 2021-04-01T23:59:59+12, for example.
locationstringFree text. Entirely optional in the POST and PATCH.
visitorAccessGroupsArray<object>These are the access groups that the server will add your visitors to after they complete induction and sign in. They define the access that your visitors have to the site, as they do for all cardholders.
It is perfectly valid to have no visitor access groups on a visit - it just means that someone will be opening all your guests' doors for them. That might be the kind of thing they are used to anyway.
The visitor access groups on a visit must be a subset of the visitor access groups on the division's visitor type (in the list of visitor access groups in the visit's reception's division's visitor management configuration, in other words).
When you send a visitor access group block to the server in a POST, just send an array of blocks each containing an href to an access group. This is one reason why you cannot copy a visit by POSTing back the JSON from a GET.
When you send a visitor access group block in a PATCH to edit an existing visit, the server
expects it to contain one or two arrays called add and remove. The add array should
contain access group hrefs, exactly as you would send when creating a visit. The remove
array can contain access group hrefs or the hrefs you received in the GET. The PATCH example
shows two hrefs trying to remove the same access group.
Your operator must have a privilege on the groups' divisions that allows viewing the access groups and adding and removing cardholders to and from them, otherwise you will be told 'Access denied when writing to one or more fields'. 'Modify Access Control' is a good choice.
visitorsArray<object>When the server sends this array to you, it contains two hrefs for each cardholder due to
arrive in this visit. The first, outside the cardholder block, is the URL you
PATCH to change the state of their visit (new in
8.90). The one inside the cardholder block is simply an href to the cardholder item. You
can use either href when removing visitors from a visit with a PATCH.
The status block contains two fields. value is a human-readable description of their
state in the server's language. type comes from a fixed enumeration.
The invitation string is opaque: please do not interpret it.
Note that a visit with no visitors on it will come out of the API but will not appear in Gallagher's visitor management application. Why clutter the screen if you're not expecting anybody, after all.
When you send a visitor block to the server in a POST to create a visit, just send an array of
blocks each containing an href to a cardholder. You do not need to put the hrefs inside
cardholder blocks, as the server sends to you, so this is the other reason you cannot copy a
visit by POSTing back the JSON from a GET.
Also do not bother sending a status block: the server will ignore it and set all visitors
to 'expected'.
When you send a visitor block in a PATCH to edit an existing visit, the server expects it to
contain an array called add, an array called remove, or both. The add array should contain
cardholder hrefs, exactly as you would send when creating a visit. The remove array should
contain either of the hrefs you received for the cardholder in the GET. The PATCH example
shows two hrefs removing the same cardholder.
Your operator must have a privilege on the groups' divisions that allows viewing the access groups and adding and removing cardholders to and from them, otherwise you will be told 'Access denied when writing to one or more fields'. 'Modify Access Control' is a good choice.
{
"name": "Visit hosted by Greta Ginger",
"href": "https://localhost:8904/api/visits/941",
"serverDisplayName": "ruatoria.satellite.int",
"description": "Initial scoping",
"division": {
"id": "2",
"href": "https://localhost:8904/api/divisions/2"
},
"reception": {
"name": "Main lobby",
"href": "https://localhost:8904/api/receptions/937"
},
"visitorType": {
"href": "https://localhost:8904/api/divisions/2/v_t/925",
"accessGroup": {
"name": "Visitor group 1",
"href": "https://localhost:8904/api/access_groups/925"
}
},
"host": {
"name": "Ginger Greeter",
"href": "https://localhost:8904/api/cardholders/526"
},
"from": "1971-03-08T14:35:00Z",
"until": "2021-03-08T14:35:00Z",
"location": "Gather in Ginger's office",
"visitorAccessGroups": [
{
"name": "Access group 22",
"href": "https://localhost:8904/api/access_groups/926"
}
],
"visitors": [
{
"href": "https://localhost:8904/api/visits/941/visitors/940",
"cardholder": {
"name": "Red Adair",
"href": "https://localhost:8904/api/cardholders/940"
},
"status": {
"value": "Expected Back",
"type": "expectedBack"
},
"invitation": "text to be encoded into a QR code for 940"
},
{
"href": "https://localhost:8904/api/visits/941/visitors/9040",
"cardholder": {
"name": "James Page",
"href": "https://localhost:8904/api/cardholders/9040"
},
"status": {
"value": "On-Site",
"type": "onSite"
},
"invitation": "text to be encoded into a QR code for 9040"
}
]
}
