Home

Command Centre REST API

v2.0
Base URL
https://127.0.0.1:8904

This 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 updates link 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:

  • unsaved and deleted are transient conditions you should not encounter in normal use. Either way, the item is in no condition to query.

  • unconfigured is 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.

  • remoteServerOffline is 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.

  • processOffline means the Controller service that is meant to be running on the Command Centre server, and handling all the communications with hardware controllers, is not.

  • controllerOffline means 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.

  • notPolled means 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.

  • deviceNotResponding means 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, unless encryptionKeysTampered accompanies 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.

  • unknown means 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.

  • controllerUnknown is 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_keyapiKey

Clients 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

basichttp

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.

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.

  1. GET /api.

  2. Follow the link at features.accessGroups.accessGroups.href (adding search terms, and setting top high to save pagination).

  3. Find your access group in the results.

  4. 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 called children. In a depth-first traversal you would recurse down through the hrefs in that array before proceeding.

  5. Get the link at cardholders (which is the same on the search results and the details page), look in that cardholder's accessGroups array, 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.

  1. GET /api.
  2. Follow the link at features.accessGroups.accessGroups.href . If you follow the efficiency tips in the cardholder section you will add top and sort parameters.
  3. 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.
  4. 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKAccessGroupSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search access groups
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/access_groups

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

application/json

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.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

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

notesstring
parentobject

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

membershipAutoRemoveExpiredboolean

If 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>
Show child attributes
namestringread only

Read-only.

hrefstring<uri-reference>

The href of the PDF definition.

Response

201Created

Success.

400Bad Request

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.

403Forbidden

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 header

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

Create an access group
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/access_groups/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnamedescriptionparentdivisionchildrennotespersonalDataDefinitionsaccesssaltoAccessalarmZones...defaultsdefaultsquery

Sets 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

200OKAccessGroupCommonFields & object & AccessGroupPDFsPOST & object

Success.

404Not Found

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 header

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

Get details of an access group
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()
200
{
  "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

DELETE
https://127.0.0.1:8904/api/access_groups/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

400Bad Request

Deleting the access group failed. This happens when it still has members.

403Forbidden

The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.

404Not Found

That is not the URL of an access group or the operator is not privileged to view it.

Authorization

API_keyapiKey in header

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

Remove an access group
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

PATCH
https://127.0.0.1:8904/api/access_groups/{id}

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

application/json

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.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

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

notesstring
parentobject

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

membershipAutoRemoveExpiredboolean

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

personalDataDefinitionsobject

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

Show child attributes
addArray<object>
Show child attributes
hrefstring<uri-reference>
removeArray<object>
Show child attributes
hrefstring<uri-reference>

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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.

409Conflict

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 header

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

Update an access group
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/access_groups/{id}/cardholders

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

idstringrequiredpath

An internal identifier.

Response

200OKobject

Success.

404Not Found

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 header

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

Get membership of an access group
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()
200
{
  "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:

  1. 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.
  2. An override without an end time lasts until the next mode change or 'cancel untimed overrides' entry in the access zone's schedule.
  3. 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:

  • saltoOutputOnly means 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.

  • mobileZone also 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 a secure flag.

  • lockedDown means 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.

  • usePin means 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.

  • zoneCountTooHigh means the zone count is above its maximum.

  • zoneCountTooLow means the zone count is below its minimum.

  • zoneCountInGrace means 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.

  • secure means the doors are locked.
  • dualAuth means the doors are locked and - depending on configuration - cardholders will need a second credential to open them.
  • codeOrCard means 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.
  • free means 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

  1. GET /api.
  2. Follow the link at features.accessZones.accessZones.href , appending a search term such as name=substring to filter the access zones, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link until there isn't one.

Changing an access zone's access mode

  1. Find the href for the access zone using the process above.
  2. GET it.
  3. Find the API URL you require in the commands structure of the results, such as free or securePin, from the detail. Use the until variants if you are specifying an end time.
  4. POST to that URL. Some require a JSON object (resetting the zone count, and all those with until in their command block keys). The others expect an empty body.

Finding an access zone's status

  1. Find the href for the access zone using the process above, and GET it.
  2. Take the updates href from that page. For a version 8.00 server append the query parameter separator (? or &) then fields=defaults,zoneCount if you need the zone count as well as the default status fields. The zone count is included by default in 8.10 and later.
  3. GET it.
  4. Use the flag rules above to interpret the status flags you receive.
  5. Follow the next link 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerdoorszoneCountstatusFlagsstatusTextstatusnotesupdatesdefaultsdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search access zones
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/access_zones/update_cardholder_location

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

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerdoorszoneCountstatusFlagsstatusTextstatusnotesupdatesdefaultsdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search targetable access zones
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/access_zones/{id}

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

idstringrequiredpath
fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerdoorszoneCountstatusFlagsstatusTextstatusnotesupdatesquery

This 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

200OKAccessZoneSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of an access zone
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/access_zones/{id}/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

application/json

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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Set access zone free
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Set access zone secure

POST
https://127.0.0.1:8904/api/access_zones/{id}/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

application/json

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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Set access zone secure
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Set access zone to code or card

POST
https://127.0.0.1:8904/api/access_zones/{id}/code_only

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

application/json

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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Set access zone to code or card
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Set access zone to dual auth

POST
https://127.0.0.1:8904/api/access_zones/{id}/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

application/json

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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Set access zone to dual auth
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Forgive antipassback on a zone

POST
https://127.0.0.1:8904/api/access_zones/{id}/forgive_anti_passback

Sends an override to an access zone to forgive anti-passback for all cardholders in the zone.

Parameters

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Forgive antipassback on a zone
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

POST
https://127.0.0.1:8904/api/access_zones/{id}/lock_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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Lock a zone down
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

POST
https://127.0.0.1:8904/api/access_zones/{id}/cancel_lock_down

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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Cancel a zone lockdown
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

POST
https://127.0.0.1:8904/api/access_zones/{id}/set_zone_count

Sets the count of cardholders inside a zone.

Body

application/json

The new cardholder count for the zone.

zoneCountintegerrequired

Put this in the body of access zone override POSTs to set the count of cardholders in the access zone.

Parameters

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Set a zone count
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()
Request Body
{
  "zoneCount": 100
}

Cancel mode override

POST
https://127.0.0.1:8904/api/access_zones/{id}/cancel

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

idstringrequiredpath

The ID of the access zone.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Cancel mode override
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

GET
https://127.0.0.1:8904/api/access_zones/{id}/updates

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

idstringrequiredpath

The ID of the access zone.

fieldsstringstatusstatusTextstatusFlagszoneCountquery

This 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

200OKobject

Success. The introduction describes the three status fields and the access zone detail describes zoneCount.

403Forbidden

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.

404Not Found

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 header

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

Monitor an access zone
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()
200
{
  "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

  1. GET /api
  2. Follow the link at features.alarms.alarms . You will receive up to 100 alarms, each containing links to its management functions.
  3. 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

GET
https://127.0.0.1:8904/api/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

fieldsstringhrefidtimemessagesourcetypeeventTypeprioritystateactivedivisionnotePresetsviewcommentacknowledgeWithCommentacknowledgeprocessWithCommentprocessdetailshistoryinstructioncardholdereventquery

Sets the fields you want in your results. Separate fields with commas.

New to 8.40.

Response

200OKAlarmSearch

Success

403Forbidden

The site does not have a RESTEvents licence.

Authorization

API_keyapiKey in header

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

Get current alarms
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()
200
{
  "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)

GET
https://127.0.0.1:8904/api/alarms/updates

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

fieldsstringhrefidtimemessagesourcetypeeventTypeprioritystateactivedivisionnotePresetsviewcommentacknowledgeWithCommentacknowledgeprocessWithCommentprocessdetailshistoryinstructioncardholdereventquery

Sets the fields you want in your results. Separate fields with commas.

New to 8.40.

Response

200OKAlarmUpdates

Success

403Forbidden

The site does not have a RESTEvents licence.

Authorization

API_keyapiKey in header

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

Get changes to alarms (or wait)
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/alarms/{id}

Full details for an alarm. Follow the href in the alarm summary to get here.

Parameters

idstringrequiredpath

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

fieldsstringhrefidtimemessagesourcetypeeventTypeprioritystateactivedivisionnotePresetsviewcommentacknowledgeWithCommentacknowledgeprocessWithCommentprocessdetailshistoryinstructioncardholdereventquery

Sets the fields you want in your results. Separate fields with commas.

New to 8.40.

Response

200OKobject & AlarmSummary

Success

404Not Found

That is not the href of an alarm or you do not have privileges for it.

Authorization

API_keyapiKey in header

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

Get details of an alarm
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/alarms/{id}/view

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

application/json

Optional comment.

commentstring

Optional for some methods that update alarms. Contains a comment placed by the operator.

Parameters

idstringrequiredpath

An 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

200OK

Success

404Not Found

That is not the href of an alarm or you do not have privileges for it.

Authorization

API_keyapiKey in header

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

Mark alarm viewed
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()
Request Body
{
  "comment": "Alarm was adequately explained."
}

Add a comment to an alarm

POST
https://127.0.0.1:8904/api/alarms/{id}/comment

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

application/json

Optional comment.

commentstring

Optional for some methods that update alarms. Contains a comment placed by the operator.

Parameters

idstringrequiredpath

An 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

200OK

Success

404Not Found

That is not the href of an alarm or you do not have privileges for it.

Authorization

API_keyapiKey in header

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

Add a comment to an alarm
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()
Request Body
{
  "comment": "Alarm was adequately explained."
}

Mark alarm acknowledged

POST
https://127.0.0.1:8904/api/alarms/{id}/acknowledge

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

application/json

Optional comment.

commentstring

Optional for some methods that update alarms. Contains a comment placed by the operator.

Parameters

idstringrequiredpath

An 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

200OK

Success

404Not Found

That is not the href of an alarm or you do not have privileges for it.

Authorization

API_keyapiKey in header

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

Mark alarm acknowledged
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()
Request Body
{
  "comment": "Alarm was adequately explained."
}

Mark alarm processed

POST
https://127.0.0.1:8904/api/alarms/{id}/process

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

application/json

Optional comment.

commentstring

Optional for some methods that update alarms. Contains a comment placed by the operator.

Parameters

idstringrequiredpath

An 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

200OK

Success

404Not Found

That is not the href of an alarm or you do not have privileges for it.

Authorization

API_keyapiKey in header

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

Mark alarm processed
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()
Request Body
{
  "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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  • armed means the alarm zone is armed. It will be called that even if you have changed the terminology in the Server Properties.

  • disarmed as above.

  • user1 will be called that even if you have given it a different name in Server Properties.

  • user2 as above.

  • exitDelay means 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.

  • entryDelay means an input has triggered the alarm zone but Command Centre is giving a cardholder the opportunity to disarm the zone.

  • lowFeel means 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).

  • highVoltage means 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

  1. GET /api.
  2. Follow the link at features.alarmZones.alarmZones.href , appending a search term such as name=substring to filter the access zones, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link until there isn't one.

Changing an alarm zone's state

  1. Find the href for the alarm zone using the process above.
  2. GET it.
  3. Find the API URL you require in the commands structure of the results, such as arm or disarm , using the detail. Use the calls with Until on the ends of their names if you are specifying an end time.
  4. POST to that URL. All those with Until in 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

  1. Find the href for the alarm zone using the process above, and GET it.
  2. Follow the updates href from that page.
  3. Use the flag rules above to interpret the status flags you receive.
  4. Follow the next link 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search alarm zones
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/alarm_zones/{id}

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

idstringrequiredpath

The ID of the alarm zone.

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusnotesupdatesquery

This 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

200OKAlarmZoneSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of an alarm zone
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/arm

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

application/json

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Arm an alarm zone
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Disarm an alarm zone

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/disarm

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

application/json

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Disarm an alarm zone
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Change an alarm zone to user1

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/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

application/json

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Change an alarm zone to user1
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Change an alarm zone to user2

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/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

application/json

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Change an alarm zone to user2
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Arm an alarm zone (high voltage)

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/armHighVoltage

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

application/json

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Arm an alarm zone (high voltage)
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Arm an alarm zone (low feel)

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/armLowFeel

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

application/json

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Arm an alarm zone (low feel)
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Cancel mode override

POST
https://127.0.0.1:8904/api/alarm_zones/{id}/cancel

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

idstringrequiredpath

The ID of the alarm zone.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Cancel mode override
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

GET
https://127.0.0.1:8904/api/alarm_zones/{id}/updates

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

idstringrequiredpath

The ID of the alarm zone.

fieldsstringstatusstatusTextstatusFlagsquery

This 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

200OKobject

Success. The introduction describes the three status fields.

403Forbidden

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.

404Not Found

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 header

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

Monitor an alarm zone
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()
200
{
  "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

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

POST
https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire

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

application/json
  • 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.
csnHexStringrequired
cadFilesobject

Optional data read from CAD files, if present on the card.

reasonstring

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OKEncryptedEncodingDataResponse | DecryptedDESFireDataForInfo | DecryptedClassicDataForInfo

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.

400Bad Request

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 credentialClass needs to be card.
  • The reason for encoding is too long (over 100 characters).

The response body will contain an error message.

403Forbidden

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.
404Not Found

The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.

422Unprocessable Entity

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 header

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

Get encoding payload for a DESFire card
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()
Request Body
{
  "csn": "0x04A23F9C123456",
  "cadFiles": {
    "0": "0x00C34F2081F4000000000000000000000000000000000000000000000000000000000000"
  },
  "reason": "New card issuance"
}
200
{
  "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

POST
https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic

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

application/json
  • 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.
csnHexStringrequired
cadClassicCad
Show child attributes
sectorintegerrequired
blocksClassicBlocksrequired

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

madClassicMad
Show child attributes
blocksClassicBlocksrequired

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

unavailableSectorsArray<integer>
reasonstring

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OKEncryptedEncodingDataResponse | DecryptedDESFireDataForInfo | DecryptedClassicDataForInfo

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.

400Bad Request

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 credentialClass needs to be card.
  • The reason for encoding is too long (over 100 characters).
403Forbidden

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.
404Not Found

The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.

422Unprocessable Entity

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 header

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

Get encoding payload for a Classic card
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()
Request Body
{
  "csn": "0x04A23F9C",
  "cad": {
    "sector": 14,
    "blocks": {
      "0": "0xadad",
      "1": "0xafad"
    }
  },
  "mad": {
    "blocks": {
      "1": "0x0ebd",
      "2": "0x0816"
    }
  },
  "unavailableSectors": [
    2,
    3,
    4
  ],
  "reason": "Lost Card Replacement"
}
200
{
  "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

POST
https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted

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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

tokenstringrequiredquery

Token provided by the encoding-data response callback link.

Response

200OK

OK

400Bad Request

Bad request: token is missing or not recognised.

403Forbidden

The site does not have a licence for the Encoding API.

404Not Found

The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.

410Gone

Gone - Callback token has expired or already been used

Authorization

API_keyapiKey in header

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

Notify Command Centre that encoding succeeded
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

POST
https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}/encodeFailed

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

application/json
messagestring

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

tokenstringrequiredquery

Token provided by the encoding-data response callback link.

Response

200OK

OK

400Bad Request

Token is missing or not recognised, or the request body is invalid.

403Forbidden

The site does not have a licence for the Encoding API.

404Not Found

The cardholder does not exist or is not visible to the operator, or that card is not on the cardholder.

410Gone

Callback token has expired or already been used.

Authorization

API_keyapiKey in header

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

Notify Command Centre that encoding failed
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()
Request Body
{
  "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

  1. GET /api.

  2. Follow the link at features.cardholders.cardholders.href , appending a search term to narrow the results and the fields parameter to add the fields you need. 3. Process the results, following the next link until there isn't one.

Downloading all cardholders

  1. GET /api

  2. Follow the link at features.cardholders.cardholders.href . This is effectively a cardholder search with no filters. You should add sort and top query parameters to sort by ID and increase the number of cardholders per page: see the efficiency tips!

  3. Process the bundle of cardholders in the result.

  4. Follow the link at next.href if 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:

  1. Get a bookmark at the head of the queue of cardholder changes.
  2. Do a one-off sync of all cardholders.
  3. 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.

  1. GET /api.

  2. v8.10 or later: follow the link at features.personalDataFields.personalDataFields.href , appending the query name="employee_id". This will return every PDF in the system with that name. There will be more than one, in rare cases.

  3. Earlier versions: follow the link at features.items.items.href , appending the query name="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 append type=33 to the item search: you will only be shown PDFs.

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

  1. Recall the JSON object you received from GET /api.
  2. Follow the link at features.cardholders.cardholders.href , adding a query separator and pdf_<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

  1. GET /api.

  2. Use the link at features.accessGroups.accessGroups.href to find the hrefs of access groups you wish to add your new cardholder to.

  3. Do the same for the competencies, relationships (roles), lockers, PDF definitions, and cards/credentials (cardTypes) that your new cardholder needs, using other URLs in the features section of /api. In v8.10, the card types that your operator has the privilege to assign are at a new URL given in the field features.cardTypes.assign.href .

  4. 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 /api page.

Modifying a cardholder

  1. Find your cardholder using one of the processes above.

  2. If you need the current values before you update them, use the fields parameter to add fields (such as accessGroups, cards, or competencies) to the search results.

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

  1. Find your cardholder using one of the processes above.

  2. Follow the link to their details page or add fields=accessGroups to the search. That will show all their access group memberships.

  3. PATCH the cardholder with the hrefs of the memberships you want to delete in an array called remove inside a block called accessGroups.

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.

  1. GET your cardholder's details page, or search with accessGroups in the field list. Either way, you will receive all their access group memberships.

  2. Take the array at accessGroups, rename it to remove, and put it in object called accessGroups at the root level of a JSON object.

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

  1. GET /api.

  2. Follow the link at features.cardTypes.cardTypes.href to find the href for the new card's type.

  3. If you want to create a card in a state other than the default, get the state from the same page.

  4. Find the href for the cardholder using one of the search methods above.

  5. Optional: follow the link at edit.href on the cardholder page. If there is a link at update.href, your REST client has permission to modify the cardholder.

  6. PATCH the cardholder's href according to the cardholder patch schema.

Deleting a cardholder's PDF value

  1. Find the URL of your cardholder using one of the processes above.
  2. PATCH your cardholder with this in the body: { "@name_of_PDF": null }

Deleting a cardholder

  1. Find your cardholder using one of the processes above.
  2. Send an HTTP DELETE to the cardholder's href.

Updating a cardholder's location

  1. Find the href of your target access zone using the link at features.cardholders.updateLocationAccessZones in the results of GET /api.

  2. Find your cardholder using one of the processes above. If you are using search, add updateLocation to the fields parameter to save you having to GET their details page later.

  3. Send an HTTP POST to that cardholder's updateLocation.href link.

Finding which access zones and doors a cardholder can access

  1. GET your cardholder's details page, or search cardholders with accessGroups in the field list. Either way, you will receive all their access group memberships.
  2. Iterate through all the access groups looking into their access blocks for access zones and schedules. Recurse up the access group hierarchy by following each group's parent.href.

Now you have the cardholder's access zones. If you want doors:

  1. For each access zone, GET its href and look in its doors block.

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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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}stringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>query

Limits 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>defaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKCardholderSearch

Success. See the note in the description about privileges if your result set is empty.

400Bad Request

The server could not make sense of your search terms.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search cardholders
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/cardholders

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

application/json

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.

firstNamestring

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

lastNamestring

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

shortNamestring
descriptionstring
authorisedboolean

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

divisionobjectrequired

Mandatory when creating any cardholder. In this example, we want all students in division 5387.

@emailstring

An example PDF value. In this example, access group 352 must include a PDF called 'email' otherwise this will appear to have no effect.

@headshotstring

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

Show child attributes
hrefstring<uri-reference>read only

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

numberstring

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

cardSerialNumberstring

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

statusobject

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

Show child attributes
valuestring

This card's state, taken from the card type's state set. The default card state set contains 'Active', 'Disabled (manually)', 'Lost', 'Stolen', and 'Damaged', or translations of those into the server's language. Your card state sets may differ, as they are customisable. (PIV cards)[#tag-PIV] have extra values.

In addition to the values in the card type's state set, it will be 'Not Yet Activated' if the card's activation date is in the future, 'Expired' if its deactivation date has passed, or 'Disabled (by inactivity)' if that is the site's policy and the card has not been used at a door.

Because the values of the value field is set by site administrators, you should not use it for programmatically determining whether a card is active. Use type instead. Use value for display.

When creating or updating a card, set value to one of the valid card states from the card state set. Command Centre is not fussy about case. If you omit it when you create a card, Command Centre will use the default for the card state set (which is 'active' for the factory state set).

If you omit both this and the PIV status when creating or updating a PIV card, or omit this and set the PIV status to 'normal' or 'offline', the server will set this to 'active'. Any other PIV status will cause the card to become inactive, regardless of what you put here. So you really only need to set this when you are changing the PIV status to 'normal' or 'offline' but you want to disable the card. In that case, set status.value to 'disabled (manually)'.

typestringpendingactiveexpiredinactiveread only

This will be 'pending' if the activation date (from) is in the future, 'expired' if its deactivation date (until) is in the past, or 'inactive' if it is disabled for one of the reasons given in the card state.

Never send type: Command Centre always infers it.

typeobject

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

Show child attributes
hrefstring<uri-reference>
namestring
invitationobject

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

Show child attributes
emailstring

The email address to which Command Centre will send or did send an invitation for this credential.

mobilestring

The telephone number to which Command Centre will send or did send an SMS containing a confirmation code for this invitation.

singleFactorOnlybooleanfalse

If you set this true Command Centre will not require a PIN or fingerprint from the cardholder when they enrol. Be aware that without that second authentication factor, zones in modes that require a PIN and readers that always require a second factor will not grant access to the cardholder.

singleFactorOnly will only be in the result of a GET if it is true. If it is false, the field will be missing.

statusstringnotSentsentexpiredacceptedread only
  • sent means Command Centre is waiting for the user to accept the invitation, either in Gallagher Mobile Connect or another app that uses the Mobile Connect SDK.

  • accepted means that the credential is ready for use.

  • notSent means the credential is only a few seconds old or Command Centre is having trouble contacting the cloud.

  • expired means that Command Centre did not receive a response in time.

hrefstring<uri-reference>read only

Mobile applications use this URL to accept this invitation. See the Mobile Connect SDK documentation for how to do that in your own applications.

Only present if status is 'sent'.

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 only

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

traceboolean

If 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 only

The 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 only

The 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 only

When a Gallagher controller last observed the credential in use.

pinstring

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

visitorContractorboolean

This 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 only

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

credentialIdstring

Reserved for use by Gallagher applications.

bleFacilityIdstring

Reserved for use by Gallagher applications.

encodingDataobjectread only

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

Show child attributes
mifareClassicobject
Show child attributes
hrefstring<uri-reference>
mifareDesfireobject
Show child attributes
hrefstring<uri-reference>
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.

Show child attributes
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.

accessGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

The two fields in this block are read-only because they are determined by the from and until dates.

Show child attributes
valuestringread only

The state of this cardholder's access group membership, in the site's language. In an English locale the value is the same as the type, but capitalised.

typestringpendingexpiredactiveread only

The state of this cardholder's access group membership.

This will be 'pending' if the activation date is set and in the future, 'expired' if its deactivation date is set and in the past, or 'active'.

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.

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.

Show child attributes
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.

operatorGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

competenciesArray<CardholderCompetency>

In this example we are giving our new cardholder a disabled competency, set to enable in January 2019.

Show child attributes
hrefstring<uri-reference>read only

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

competencyobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

This 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 flagEnablement dateExpiry datestatus.type
true-Far futureactive
true-Near futureexpiryDue
true-Pastexpired
falseFuture-pending
falsePast-inactive
falseUnset-inactive
Show child attributes
valuestring
typestring
disabledbooleanread onlydeprecated

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

enabledboolean

This 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 only

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

commentstring

The comment appears in the management clients when viewing the cardholder.

limitedCreditboolean

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

creditinteger

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

notesstring
notificationsobject

You can set or update any of the three fields in this block.

Show child attributes
enabledboolean
fromstring<date-time>
untilstring<date-time>
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.

Show child attributes
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.

roleobject

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

cardholderobject

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

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.

Show child attributes
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.

lockerobject

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

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.

Show child attributes
hrefstring<uri-reference>

The href of this cardholder's elevator group entry. DELETE this to remove it from the cardholder.

elevatorGroupobject

The href and name of the elevator group for which this cardholder has a default floor or passenger type.

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

accessZoneobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

enableCaptureFeaturesboolean

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

enableCodeBlueFeaturesboolean

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

enableExpressFeaturesboolean

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

enableServiceFeaturesboolean

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

enableService2Featuresboolean

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

enableService3Featuresboolean

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

enableVipFeaturesboolean

VIP operation allows cardholders to swipe a card or enter a PIN to isolate the elevator and provide uninterrupted access to their designated floor.

Response

201Created

Success.

400Bad Request

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.

403Forbidden

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 header

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

Create a cardholder
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()
Request Body

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

GET
https://127.0.0.1:8904/api/cardholders/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>defaultsquery

Specifies 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

200OKCardholderSummary & CardholderCommonFields & object

Success.

404Not Found

That is not the URL of a cardholder, or the operator does not have the privilege to view that cardholder.

Authorization

API_keyapiKey in header

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

Get details of a cardholder
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()
200
{
  "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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

400Bad Request

Deleting the cardholder failed. This happens when the cardholder is a critical part of another construct (a personalised notification, for example).

403Forbidden

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.

404Not Found

New in 9.30. There is no such cardholder.

409Conflict

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 header

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

Remove a cardholder
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

PATCH
https://127.0.0.1:8904/api/cardholders/{id}

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

application/json

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.

authorisedboolean
firstNamestring
@employeeIdstring

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

cardsobject

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

accessGroupsobject

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

competenciesobject

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

relationshipsobject

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

lockersobject

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

operatorGroupsobject

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

elevatorGroupsobject

This 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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will add feedback from the server about your PATCH.

204No Content

Success, with no feedback.

400Bad Request

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.

403Forbidden

The site does not have a RESTCardholders licence, or you attempted to set a PDF for which you have no privilege.

409Conflict

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 header

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

Update a cardholder
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()
Request Body

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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/access_groups/{secondary_id}

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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

204No Content

Success.

400Bad Request

The operator does not have sufficient privilege.

403Forbidden

The operator does not have sufficient privilege.

404Not Found

That is not the href of an access group membership.

409Conflict

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 header

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

Remove an access group membership
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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/cards/{secondary_id}

This call removes a card from a cardholder.

You can find this URL in the cardholder object. Do not build it yourself.

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

403Forbidden

The operator does not have a privilege that allows editing that cardholder.

404Not Found

That card is not on that cardholder.

409Conflict

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 header

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

Remove a card from a cardholder
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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}

This call removes a competency from a cardholder.

You can find this URL in the cardholder object. Do not build it yourself.

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

403Forbidden

The operator does not have a privilege that allows editing that cardholder.

404Not Found

That is not the href of a cardholder's competency.

Authorization

API_keyapiKey in header

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

Remove a competency from a cardholder
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

POST
https://127.0.0.1:8904/api/cardholders/{id}/competencies/{secondary_id}/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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

addintegerrequiredquery

The amount to adjust the competency credit. This can be positive or negative.

Response

200OK

Success. Future versions will return feedback from the server.

204No Content

Success.

403Forbidden

The operator does not have a privilege that allows editing that cardholder.

404Not Found

The parameters are invalid.

Authorization

API_keyapiKey in header

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

Change competency credit
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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/elevator_groups/{secondary_id}

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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

204No Content

Success.

403Forbidden

Your operator does not have the necessary privilege to change this cardholder's elevator groups.

404Not Found

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 header

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

Remove an elevator group from a cardholder
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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/lockers/{secondary_id}

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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

204No Content

Success.

400Bad Request

You do not have privileges for the operation.

403Forbidden

You do not have privileges for the operation.

404Not Found

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.

409Conflict

You do not have privileges for the operation.

Authorization

API_keyapiKey in header

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

Remove a locker assignment
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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/operator_groups/{secondary_id}

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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

204No Content

Success.

403Forbidden

The operator does not have a privilege that allows editing that cardholder's operator group memberships ('Modify operator group membership').

404Not Found

That is not the href of an operator group membership.

409Conflict

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 header

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

Remove an operator group membership
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

DELETE
https://127.0.0.1:8904/api/cardholders/{id}/roles/{secondary_id}

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

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

204No Content

Success.

403Forbidden

The operator does not have a privilege that allows editing that cardholder.

404Not Found

That is not the URL of a relationship. Perhaps it is deleted already.

Authorization

API_keyapiKey in header

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

Remove a relationship
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

POST
https://127.0.0.1:8904/api/cardholders/{id}/update_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

application/json

The body of the request must contain the href of the target access zone.

accessZoneobject
Show child attributes
hrefstring<uri-reference>

The href of the access zone into which you want to move your cardholder. Use an href from the access zones controller, or the items controller if you do not have a RESTStatus or (in 8.60) RESTOverrides licence.

In 9.60 and later, use the empty string "" to move a cardholder out of all zones (known as "outside the system").

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server.

204No Content

Success.

400Bad Request

The cardholder ID or access zone href is invalid.

403Forbidden

The operator does not have a privilege ('Manage Cardholder Location') that allows moving this cardholder to the target zone.

Authorization

API_keyapiKey in header

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

Change a cardholder's location
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/cardholders/{id}/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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

403Forbidden

You do not have privileges for the operation.

Authorization

API_keyapiKey in header

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

Find out which fields you can edit
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.

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

    1a. GET /api

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

    1c. Take note of the href in the next block. This is your bookmark. Later on you will use it to ask for all the changes that happen after this point.

  2. If you are filling a user directory from Command Centre, download all cardholders using this advice. Here is a reminder:

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

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

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

  4. If the results array you get back is empty you are up to date and you have nothing to do. Sleep for a time before your next poll, to avoid a tight loop and ease the load on your database. When it comes time to poll again, do not be tempted to resubmit the same request: the URL to use sometimes changes even when there are no results, so always get it from next.href.

  5. If the results array is not empty it will contain the first changes that happened after your previous call. The sections below will help you process them.

  6. 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 lastSuccessfulAccess but does not count as a cardholder change.

  • The API will return the pre-change values of some fields, but not those that require a lot of storage such as image PDFs and PIV certificates. Command Centre does not hang on to them after they change.

Get changes

GET
https://127.0.0.1:8904/api/cardholders/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 filter parameter. If you are only interested in people's access group memberships, for example, add filter=accessGroups to 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 need item so you can tell which cardholder changed. If you store your own identifier in a PDF you could drop item and add cardholder.pdf_XXX, where 'XXX' is the ID of your PDF.

  • Sleep for as long as you can between calls.

  • If you are only interested in changes to cardholders in certain divisions, only give your REST operator access to those divisions. It will not see changes outside them.

Parameters

topinteger>= 1query

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

filterArray<string>hrefidfirstNamelastNameshortNamedescriptionauthorisedlastSuccessfulAccessZonedivisionnotespersonalDataFieldsoperatorLoginEnabledoperatorUsernameoperatorPasswordoperatorPasswordExpiredwindowsLoginEnabledwindowsUsernamecardsaccessGroupscompetenciesnotificationsrelationshipslockersdefaultsdefaultsquery

Limits 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.*defaultsdefaultsquery

Limits 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>= 050query

Sets 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

200OKCardholderChanges

Success.

403Forbidden

The site does not have the RESTCardholders licence.

500Internal Server Error

The server was too busy to complete the request before the deadline.

Authorization

API_keyapiKey in header

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

Get changes
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()
200
{
  "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:

  1. GET /api.
  2. If running 8.00 or earlier, follow the link at features.cardTypes.cardTypes.href (which will be to /api/card_types), or
  3. 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).
  4. Iterate through the array to find the element with credentialClass: piv, and
  5. 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

GET
https://127.0.0.1:8904/api/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

200OK

Success.

403Forbidden

The installation lacks a cardholders licence.

Authorization

API_keyapiKey in header

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

Search card types
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]

POST
https://127.0.0.1:8904/api/card_types

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

application/json

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 only

This is the identifier to use when assigning a card to a cardholder in a cardholder PATCH or POST.

This is also the URL for the card type's detail page. GETting it will return you a 404 if you do not have 'View site' or 'Configure site' on the card type's division.

idstringread only

The API does not use this field. Nor should you.

namestring
divisionobject

The division that contains this card type. Required when creating a card type.

notesstring

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

facilityCodestring

A 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 only

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

credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransact

Required when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.

minimumNumberstring

For card types with integer card numbers, this is the minimum. Must be non-negative.

maximumNumberstring

For card types with integer card numbers, this is the maximum. Must be non-negative.

serverDisplayNamestringread only

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

regexstring

This is the regular expression that a text card number must match before Command Centre will accept it.

regexDescriptionstring

Regular expressions often need explaining to your users.

cardStateSetany

Documentation coming. TODO

POST only.

defaultIssueLevelany

Documentation coming. TODO

cardIssueLevelMustMatchDefaultany

Documentation coming. TODO

enableExpiryNotificationsany

Documentation coming. TODO

warningPeriodany

Documentation coming. TODO

pinLengthany

Documentation coming. TODO

inactivityTimeoutInDaysany

Documentation coming. TODO

allowReprintany

Documentation coming. TODO

allowReEncodeany

Documentation coming. TODO

isEngageany

Documentation coming. TODO

sendRegistrationEmailany

Documentation coming. TODO

sendRegistrationSmsany

Documentation coming. TODO

warningPeriodTypeany

Documentation coming. TODO

Days, Weeks, Months, or Years.

cardNumberFormatany

Documentation coming. TODO

POST only.

defaultExpiryTypeany

Documentation coming. TODO

ExplicitDateTime or Duration.

defaultExpiryDurationany

Documentation coming. TODO

(if the type is duration)

defaultExpiryFromany

Documentation coming. TODO

defaultExpiryUntilany

Documentation coming. TODO

(if defaultExpiryType is explicitDateTime)

engageCardFormatany

Documentation coming. TODO

Only if isEngage is true. There are nine acceptable values.

appleTemplateIdany

Documentation coming. TODO

Required if credential class is Apple Pass.

appleLinkedCredentials |any

Documentation coming. TODO

The POST format is different from the PATCH format's add and remove arrays.

Response

201Created

Success.

400Bad Request

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.

403Forbidden

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 header

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

Create a card type [coming]
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/card_types/assign

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

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnamedivisionfacilityCodeavailableCardStatescredentialClassminimumNumbermaximumNumbernotesregexregexDescriptionquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKCardTypeSearch

Success.

403Forbidden

The installation lacks a cardholders licence.

Authorization

API_keyapiKey in header

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

Search usable card types
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/card_types/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

403Forbidden

The installation lacks a cardholders licence.

Authorization

API_keyapiKey in header

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

Get one card type
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]

DELETE
https://127.0.0.1:8904/api/card_types/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

400Bad Request

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.

403Forbidden

The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.

404Not Found

That is not the URL of a card type, or the operator is not privileged to view it.

Authorization

API_keyapiKey in header

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

Remove a card type [coming]
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]

PATCH
https://127.0.0.1:8904/api/card_types/{id}

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

application/json

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 only

This is the identifier to use when assigning a card to a cardholder in a cardholder PATCH or POST.

This is also the URL for the card type's detail page. GETting it will return you a 404 if you do not have 'View site' or 'Configure site' on the card type's division.

idstringread only

The API does not use this field. Nor should you.

namestring
divisionobject

The division that contains this card type. Required when creating a card type.

notesstring

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

facilityCodestring

A 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 only

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

credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransact

Required when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.

minimumNumberstring

For card types with integer card numbers, this is the minimum. Must be non-negative.

maximumNumberstring

For card types with integer card numbers, this is the maximum. Must be non-negative.

serverDisplayNamestringread only

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

regexstring

This is the regular expression that a text card number must match before Command Centre will accept it.

regexDescriptionstring

Regular expressions often need explaining to your users.

cardStateSetany

Documentation coming. TODO

POST only.

defaultIssueLevelany

Documentation coming. TODO

cardIssueLevelMustMatchDefaultany

Documentation coming. TODO

enableExpiryNotificationsany

Documentation coming. TODO

warningPeriodany

Documentation coming. TODO

pinLengthany

Documentation coming. TODO

inactivityTimeoutInDaysany

Documentation coming. TODO

allowReprintany

Documentation coming. TODO

allowReEncodeany

Documentation coming. TODO

isEngageany

Documentation coming. TODO

sendRegistrationEmailany

Documentation coming. TODO

sendRegistrationSmsany

Documentation coming. TODO

warningPeriodTypeany

Documentation coming. TODO

Days, Weeks, Months, or Years.

cardNumberFormatany

Documentation coming. TODO

POST only.

defaultExpiryTypeany

Documentation coming. TODO

ExplicitDateTime or Duration.

defaultExpiryDurationany

Documentation coming. TODO

(if the type is duration)

defaultExpiryFromany

Documentation coming. TODO

defaultExpiryUntilany

Documentation coming. TODO

(if defaultExpiryType is explicitDateTime)

engageCardFormatany

Documentation coming. TODO

Only if isEngage is true. There are nine acceptable values.

appleTemplateIdany

Documentation coming. TODO

Required if credential class is Apple Pass.

appleLinkedCredentials |any

Documentation coming. TODO

The POST format is different from the PATCH format's add and remove arrays.

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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.

409Conflict

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 header

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

Update a card type [coming]
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()
Request Body
{
  "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 operator href. 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="" or pdf_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_279 and type_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 sort parameter. 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 normal and faulty, 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 hasLocation greatly simplifies the monitoring of cardholder movements

  • A new (location block on events greatly simplifies interpreting them.

  • You can search for cardholders by the access zone they are in.

  • GovPass cards have two new fields, visitorContractor and ownedBySite.

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 eventType containing the ID and name of their type. The type field, which has different contents for alarms and events, is now obsolete (but still returned in results).

  • Alarms contain a new field called event containing a link to the corresponding event, mirroring the existing alarm field 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.

  • Redactions.

  • An optional PDF field operatorAccess contains the level of access your operator has to cardholders' values for the PDF.

  • Card type regex and regexDescription fields.

  • 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 relatedItem query 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 contentType that returns their MIME type. This deprecates the old imageFormat field, which is non-standard.

  • Asking for a particular PDF on a cardholder (by putting pdf_XXX into the fields query 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_by lets 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/competencies 8.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 fields query 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 modifiedItem block, deprecating the accessGroup block 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 serverDisplayName field.

  • 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 door block.

  • 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 trace indicating 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 name field 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_location on 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 name field, 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 fields query 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

  • Incoming events

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

DELETE
https://127.0.0.1:8904/api/changelog

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

200OK

This result is unlikely.

404Not Found

This path does not exist.

Authorization

API_keyapiKey in header

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

Dummy path for changelog tag.
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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnameshortNamedescriptiondivisionnotesexpiryNotifynoticePerioddefaultExpirydefaultAccessdefaultsdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKCompetencySearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The installation lacks a cardholders licence.

Authorization

API_keyapiKey in header

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

Search competencies
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/competencies

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

application/json

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.

namestring

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

shortNamestring

If you supply a string that is too long, Command Centre will truncate it."

descriptionstring

The new item's description.

divisionobject

The division to contain this competency.

Mandatory when creating a new competency.

notesstring

A string, able to me much longer than description, suitable for holding notes about the item.

Response

201Created

Success. Check the response body for feedback about your request.

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

403Forbidden

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 header

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

Create a competency
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/competencies/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnameshortNamedescriptiondivisionnotesexpiryNotifynoticePerioddefaultExpirydefaultAccessdefaultsdefaultsquery

Specifies 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

200OKCompetencySummary & object

Success.

403Forbidden

The installation lacks a cardholders licence.

404Not Found

Your REST operator does not have the privilege to view this competency.

Authorization

API_keyapiKey in header

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

Get details of a competency
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()
200
{
  "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

DELETE
https://127.0.0.1:8904/api/competencies/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

400Bad Request

Deleting the competency failed. This happens when it is associated with a cardholder - even if it is inactive.

403Forbidden

The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.

404Not Found

That is not the URL of a competency, or the operator is not privileged to view it.

Authorization

API_keyapiKey in header

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

Remove a competency
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

PATCH
https://127.0.0.1:8904/api/competencies/{id}

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

application/json

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.

namestring

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

shortNamestring

If you supply a string that is too long, Command Centre will truncate it."

descriptionstring

The new item's description.

divisionobject

The division to contain this competency.

Mandatory when creating a new competency.

notesstring

A string, able to me much longer than description, suitable for holding notes about the item.

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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.

409Conflict

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 header

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

Update a competency
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()
Request Body
{
  "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

  1. GET /api.

  2. Follow the link at features.dayCategories.dayCategories.href, appending a search term such as name=substring or name="full name" to filter the selection, and fields to tell the server what to return about each day category.

  3. Search the results for the day category you are after. With other types of items you would follow the next link 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 set top=1000, which is advised.

Licensing

Access to day categories requires one of RESTCardholders, RESTStatus, RESTOverrides, or RESTConfiguration.

Search day categories

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>hrefnamedescriptionnotesdefaultsdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The site does not have the RESTCardholders, RESTStatus, or RESTOverrides licence.

Authorization

API_keyapiKey in header

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

Search day categories
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/divisions

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

application/json

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.

namestring

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

descriptionstring

The division's description.

notesstring

A string, able to be much longer than description, suitable for holding notes about the division.

parentobject

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

Show child attributes
hrefstring<uri-reference>

Response

201Created

Success. Check the response body for feedback about your request.

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

403Forbidden

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 header

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

Create a division
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()
Request Body
{
  "name": "Long division",
  "description": "Quatermasters",
  "notes": "A very long string.",
  "parent": {
    "href": "https://localhost:8904/api/divisions/2"
  }
}

List divisions

GET
https://127.0.0.1:8904/api/divisions/_operation_

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>= 1query

Sets the maximum number of divisions to return per page.

sortstringidname-id-namequery

Changes 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:

  1. 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.
  2. Following a next link 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.

fieldsstringhrefidnameparentvisitorManagementquery

Return 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

200OKDivisions

Success

403Forbidden

The site does not have a REST licence.

Authorization

API_keyapiKey in header

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

List divisions
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/divisions/{id}

Details of a division. Follow the href in a division summary to get here.

Parameters

idstringrequiredpath

An internal identifier.

fieldsstringhrefidnameparentvisitorManagementquery

Return 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

200OKDivision

Success

403Forbidden

The site does not have a REST licence.

404Not Found

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 header

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

Get details of a division
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()
200
{
  "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

DELETE
https://127.0.0.1:8904/api/divisions/{id}

This call removes a division from Command Centre.

Deleting divisions will be possible in a future version of Command Centre.

Parameters

idstringrequiredpath

An internal identifier.

Response

204No Content

Success.

400Bad Request

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

403Forbidden

The operator has the permission to view the division but not delete it, or the server is not licensed for the operation.

404Not Found

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 header

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

Remove a division
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

PATCH
https://127.0.0.1:8904/api/divisions/{id}

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

application/json

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.

namestring

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

descriptionstring

The division's description.

notesstring

A string, able to be much longer than description, suitable for holding notes about the division.

parentobject

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

Show child attributes
hrefstring<uri-reference>

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. The response body will contain feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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.

409Conflict

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 header

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

Update a division
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()
Request Body
{
  "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:

  • forced means the door was opened or unlocked while secure.
  • openTooLong means the door has been open for longer than its configured DOTL time.
  • tamper means 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.
  • open means the door has a sensor for detecting its openness and it is reporting as such.
  • closed is the inverse. The door is closed or has no open sensor.
  • locked means 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.
  • unlocked means 'locked' is not set.
  • secure means the door's normal state is closed and locked.
  • free is 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

  1. GET /api.
  2. Follow the link at features.doors.doors.href, appending a search term such as name=substring to filter the selection if you have a lot of doors, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link until there isn't one.

Opening a Door

  1. Find the href for the door using the process above.
  2. GET it.
  3. POST to the open URL in the commands structure of the results.

Finding a door's status

  1. Find the href for the door using the process above, and GET it.
  2. Follow the updates href from that page.
  3. Use the flag rules above to interpret the status flags you receive.
  4. Follow the next link 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerentryAccessZoneexitAccessZonestatusFlagsstatusTextstatusnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKDoorSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search doors
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/doors/{id}

This returns the detail of one door.

Follow the 'href' field in a door summary to get here.

Parameters

idstringrequiredpath

An internal identifier.

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerentryAccessZoneexitAccessZonestatusFlagsstatusTextstatusnotesupdatesdefaultsquery

This 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

200OKDoorSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of a door
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/doors/{id}/lock

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

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Lock a door
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

POST
https://127.0.0.1:8904/api/doors/{id}/open

Sends an override to unlock a door.

Follow the commands.open.href field in a door to get here.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Open a door
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

GET
https://127.0.0.1:8904/api/doors/{id}/updates

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

idstringrequiredpath

An internal identifier.

fieldsstringstatusstatusTextstatusFlagsquery

This 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

200OKobject

Success. See the introduction for a description of the three status fields.

403Forbidden

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.

404Not Found

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 header

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

Monitor a door
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()
200
{
  "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

  1. GET /api.
  2. Follow the link at features.cardholders.modifyPassengerDetails.href, appending a search term such as name=substring to filter the selection if you have a lot of elevator groups.
  3. Find the elevator group you are after, following the next link if you have lots.
  4. Look in the floorAccess array 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

  1. Find the href for the cardholder.

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

GET
https://127.0.0.1:8904/api/elevator_groups/modify_passenger_details

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

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefnamedescriptiondivisionfloorAccessdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

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.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search assignable elevator groups
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefnameshortNamedescriptiondivisionnoteselevatorSystemelevatorGroupNumberfloorAccessrearAccessEnabledgroundFloorNumberdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

Success. An array of elevator group objects and a next link for more.

403Forbidden

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 header

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

Search elevator groups
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/elevator_groups/{id}

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

idstringrequiredpath

An internal identifier.

fieldsstringhrefnameshortNamedescriptiondivisionnoteselevatorSystemelevatorGroupNumberfloorAccessrearAccessEnabledgroundFloorNumberdefaultsquery

This 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

200OKElevatorGroupSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of an elevator group
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()
200
{
  "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 hasLocation that lets you filter for only those events that reveal a cardholder's location. If your server is 9.00 or better, use it in the type query 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 location block 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

  1. GET /api
  2. 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 use fields to get the next link for each event. Note that this is different from the next link you receive at the end of each result set.
  3. Process the events you receive in that call. If you asked for the next field, 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.
  4. If there were results, follow the link at next.href and 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

  1. Get /api
  2. Take the link at features.events.events.href and append previous=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.
  3. Process the events you receive in that call.
  4. Follow the link at previous.href to get earlier events, or next.href or updates.href for 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.

  1. GET /api
  2. Follow the link at features.events.events.href appending after=2017-01-01Z&before=2017-02-01Z or 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

  1. GET /api
  2. 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.
  3. Process the events you receive in that call. Often there is only one: the first that arrived after you made the call.
  4. Sleep to reduce load on the server.
  5. Loop, following the link at updates.href after 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 the next field, but you will be using the updates field, which is the same thing in long-poll form.

Receiving new events as they occur, starting from now, with no long polls

  1. GET /api
  2. Follow the link at features.events.events.href and append previous=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 a next link you'll need later.
  3. Sleep and loop, following the link at next.href after 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

  1. GET /api
  2. Follow the link at features.events.events.href appending the query parameter after=2021-05-08Z (or whatever timestamp is appropriate).
  3. Process the events you receive in that call.
  4. Sleep to reduce load on the server.
  5. Loop, following the link at updates.href after 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 the next field, but you will be using the updates field, which is the same thing in long-poll form.
  1. GET /api

  2. Follow the link at features.items.items.href , adding name="your_cardholder_name"&type=1 to 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 and name="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.

  3. Extract the item ID of your cardholder or cardholders from that page, repeating as necessary for additional cardholders.

  4. Follow the link on the /api page at features.events.events.href appending the separator and cardholder=XX or cardholder=XX,YY,ZZ with 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:

  1. GET /api

  2. Get the URL of the head of the event queue by following the link at features.events.updates.href appending type=hasLocation&fields=location with 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.

  3. 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 location block. 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 the next field, but if you want long polls you will be using the updates field.

  4. Sleep for a short time to reduce load on the server.

  5. Follow the link at updates.href for a long poll that waits for new events to arrive, or follow next.href for a call that will return immediately even if there are no new events for you.

  6. 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 afterLocation block inside location. The canonicalTypeName field in there will tell you what kind of location it is (reception or access zone). The schema definition describes afterLocation in 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.type of moved. Look at the afterLocation to see where they landed. There will also be a beforeLocation if the door had two zones configured on it, in case you're interested in where they came from. The canonicalTypeName field 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.type of denied. Like 'moved'-type events, location.afterLocation will 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

  1. GET /api
  2. 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

  1. GET /api
  2. 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 top at 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 if top is too low.

  • If using 8.40 or later, use its field query 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 updates link 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 calling updates ten 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 versionValid fields parameter values
Older than 8.20defaults,cardholder.pdf_XXXX
8.20 to 8.30defaults,cardholder.pdf_XXXX
or
defaults,details
or
defaults,cardholder.pdf_XXXX,details
8.40 and laterany

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

GET
https://127.0.0.1:8904/api/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]1000query

Sets the maximum number of events to return per page.

afterstring<date-time>query

Restricts 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>query

Restricts 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>query

Restricts 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>query

Restricts 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>query

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

cardholderstringquery

Restricts events to those associated with the cardholder that has this Command Centre ID. Separate multiple IDs with commas.

divisionArray<string>query

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

directDivisionstringquery

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

relatedItemstringquery

Restrict events to those associated with the item that has this Command Centre ID. Separate multiple IDs with commas.

Example: relatedItem=3,102

fieldsstringdefaultsdetailscardholder.pdf_*hrefidserverDisplayNametimemessageoccurrencespriorityalarmoperatorsourcegrouptypeeventTypedivisioncardholderentryAccessZoneexitAccessZonedooraccessGroupcardmodifiedItemlastOccurrenceTimepreviousnextupdateslocationquery

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

previousbooleanfalsequery

Returns 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>= 0query

INTERNAL 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

200OKEventSearch

Success

403Forbidden

The site does not have a RESTEvents licence.

Authorization

API_keyapiKey in header

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

Search events
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/events

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

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:

  1. 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.
  2. 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.
  3. 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

application/json

You can specify many things on an event but the only mandatory field is the type. When you are developing, start with just that.

typeobjectdeprecated

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

Show child attributes
hrefstring<uri-reference>
eventTypeanyrequired

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

Show child attributes
hrefstring<uri-reference>

Take this href from the list of event types. Note that you can only use event types in one of the thirty external event groups with IDs 57-66 and 190-209. Command Centre ships with one event type per group, IDs 4000-4009 and 6010-6029, but you can create 970 more using the External Event Type Configuration utility.

sourceobject

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

Show child attributes
hrefstring<uri-reference>

Get the href using the API controller for that type of item (such as doors) or from the more general item search.

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

messagestring

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

detailsstring

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

cardholderobject

If you wish to attach a cardholder to your event, link it here. Reports can show or filter by the cardholder.

Show child attributes
hrefstring<uri-reference>

This can be the href of any cardholder to which your operator has view access. Get the href from the cardholders controller.

operatorobject

If you wish to attach an operator to your event, link it here. Like the cardholder, reports can show or filter by the operator.

Show child attributes
hrefstring<uri-reference>

This can be the href of any cardholder to which your operator has view access. It does not need to be an operator (a member of an operator group).

entryAccessZoneobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any access zone to which your operator has view access. Get the href using the access zones controller or from /items.

accessGroupobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any access group to which your operator has view access. Get the href using the groups controller or the items controller.

lockerBankobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any locker bank to which your operator has view access. Get the href using the locker banks calls or the more general items routes.

lockerobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any locker your operator can view. Get the href from the items controller or the lockers field of a locker bank.

doorobject

If you wish to link a door to your event for later extraction or a report filter, do it here.

Show child attributes
hrefstring<uri-reference>

This can be the href of any door your operator can see. Get it with a doors call or a more general items search.

Response

201Created

Success.

204No Content

Success with no event created, probably because the priority in the action plan was zero.

400Bad Request

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.

403Forbidden

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 header

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

Add an event
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()
Request Body

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)

GET
https://127.0.0.1:8904/api/events/updates

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.href in the results of GET /api, it will block until at least one event arrives that meet your search criteria, then return them.

  • If you follow the updates link from the results of GET /api/events or GET /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]query

Sets the maximum number of events to return per page.

deadlineinteger[1, 86400]query

Sets 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>query

Removes 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>query

Removes 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>query

Restricts 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>query

Restricts 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>query

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

cardholderstringquery

Restricts events to those associated with the cardholder with this Command Centre ID. Separate multiple IDs with commas.

Example: cardholder=325

divisionstringquery

Restricts events to those in the division with this ID and its descendant divisions. Separate multiple IDs with commas.

Example: division=2,101

fieldsstringdefaultsdetailscardholder.pdf_*hrefidserverDisplayNametimemessageoccurrencespriorityalarmoperatorsourcegrouptypeeventTypedivisioncardholderentryAccessZoneexitAccessZonedooraccessGroupcardmodifiedItemlastOccurrenceTimepreviousnextupdateslocationquery

In 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>= 0query

INTERNAL 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

200OKEventSearch

Success

403Forbidden

The site does not have a RESTEvents licence.

Authorization

API_keyapiKey in header

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

Get new events (or wait)
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/events/{id}

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

idstringrequiredpath

An internal identifier.

fieldsstringdefaultsdetailscardholder.pdf_*hrefidserverDisplayNametimemessageoccurrencespriorityalarmoperatorsourcegrouptypeeventTypedivisioncardholderentryAccessZoneexitAccessZonedooraccessGroupcardmodifiedItemlastOccurrenceTimepreviousnextupdateslocationquery

In 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

200OKEventSummary & object

Success

403Forbidden

The site does not have a RESTEvents licence.

404Not Found

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 header

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

Get details of an event
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/events/groups

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

200OKEventGroups

Success

403Forbidden

The site does not have the RESTEvents or RESTCreateEvents licence.

Authorization

API_keyapiKey in header

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

List event types
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()
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"
        }
      ]
    }
  ]
}

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:

  • notPolled means the fence zone is shunted, which means intentionally ignored, and is essentially offline.
  • overridden means just that.
  • deterrentUnknown means 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).
  • on means the fence zone is live, energised. Take care.
  • off means the fence zone is off.
  • lowFeel means the fence energiser is delivering enough voltage to detect disturbances. Depending on the fence zone's configuration, it may also be a deterrant.
  • highVoltage means the pulse is delivering a deterrant pulse.
  • hVPlusMode means 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.
  • serviceMode means 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.
  • voltageKnown means 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.
  • alert means the fence voltage is outside the alert range. Typically this means an electical problem.
  • warning means the fence voltage is between the alert and warning ranges. Typically this means it has grounded somewhere.
  • preArm means the zone is performing its pre-arm check.
  • lockedOut means the zone has been locked out at a keypad.
  • parentAlert means 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

  1. GET /api.
  2. Follow the link at features.fenceZones.fenceZones.href , appending a search term such as name=substring to filter the fence zones, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link until there isn't one.

Overriding a fence zone

  1. Find the href for the fence zone using the process above.
  2. GET it.
  3. Find the API URL you require in the commands structure of the results, such as off, highVoltage, or shunt, from the detail.
  4. POST to that URL. You do not need to send anything in the body of the POST.

Finding a fence zone's status

  1. Find the href for the fence zone using the process above, and GET it.
  2. Take the updates href from that page. If you are after the fence's voltage and are using version 8.00, append fields=defaults,voltage (after a ? or &). You do not need that for later versions as voltage became a default field in 8.10.
  3. GET it.
  4. Use the flag rules above to interpret the status flags you receive.
  5. Follow the next link 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllervoltagestatusFlagsstatusTextstatusnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKFenceZoneSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search fence zones
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/fence_zones/{id}

This returns the detail of one fence zone.

Follow the 'href' field in a fence zone summary to get here.

Parameters

idstringrequiredpath

An internal identifier.

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllervoltagestatusFlagsstatusTextstatusnotesupdatesdefaultsquery

This 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

200OKFenceZoneSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of a fence zone
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/on

Sends an override to an alarm zone to turn it on until the next scheduled or manual change.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Turn on a fence zone
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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/off

Sends an override to an alarm zone to turn it off until the next scheduled or manual change.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Turn off a fence zone
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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/shunt

Sends an override to an alarm zone to shunt it, effectively preventing all communication with it.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Shunt a fence zone
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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/unshunt

Sends an override to an alarm zone to unshunt it, re-enabling its communication.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Unshunt a fence zone
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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/high_voltage

Sends an override to an alarm zone to change it to high voltage mode until the next scheduled or manual change.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Change to high voltage
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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/low_feel

Sends an override to an alarm zone to change it to 'low feel' mode until the next scheduled or manual change.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Change to low feel
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

POST
https://127.0.0.1:8904/api/fence_zones/{id}/cancel

Cancels an active override.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Cancel an override
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

GET
https://127.0.0.1:8904/api/fence_zones/{id}/updates

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

idstringrequiredpath

An internal identifier.

fieldsanystatusstatusTextstatusFlagsvoltagequery

This 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

200OKobject

Success. See the introduction for a description of the three status fields and the detail page for voltage.

403Forbidden

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.

404Not Found

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 header

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

Monitor a fence zone
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()
200
{
  "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:

  • closed means the input circuit is closed.

  • open means the input circuit is open.

  • tamper means the input circuit is shorted or open.

  • notPolled means the input is shunted, which means intentionally ignored, and is essentially offline.

  • isolated means 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

  1. GET /api.
  2. Follow the link at features.inputs.inputs.href, appending a search term such as name=substring to select the inputs, top if you expect lots of them, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link if there is one.

Overriding an Input

  1. Find the href for the input using the process above.
  2. GET it.
  3. Find the API URL for the override you need in the commands structure of the results.
  4. POST to that URL with an empty body.

Finding an input's status

  1. Find the href for the input using the process above, and GET it.
  2. Take the updates href from that page.
  3. GET it.
  4. Use the flag rules above to interpret the status flags you receive.
  5. Follow the next link 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKInputSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search inputs
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/inputs/{id}

This returns the detail of one input.

Follow the 'href' field in an input summary to get here.

Parameters

idstringrequiredpath

An internal identifier.

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsquery

This 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

200OKInputSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of an input
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/inputs/{id}/shunt

Sends an override to shunt an input, preventing all communication.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Shunt an input
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

POST
https://127.0.0.1:8904/api/inputs/{id}/unshunt

Sends an override to unshunt an input, re-enabling communication.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Unshunt an input
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

POST
https://127.0.0.1:8904/api/inputs/{id}/isolate

Sends an override to isolate an input. An isolated input will not prevent an alarm zone from arming.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Isolate an input
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

POST
https://127.0.0.1:8904/api/inputs/{id}/deisolate

Sends an override to end the isolation of an input.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

De-isolate an input
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

GET
https://127.0.0.1:8904/api/inputs/{id}/updates

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

idstringrequiredpath

An internal identifier.

fieldsstringstatusstatusTextstatusFlagsquery

This 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

200OKobject

Success. See the introduction for a description of the three status fields.

403Forbidden

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.

404Not Found

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 header

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

Monitor an input
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()
200
{
  "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:

  • secure means 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.

  • open means 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.

  • overridden means the interlock group has received a disable override. Doors will open as normal.

  • forced means 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

  1. GET /api.
  2. Follow the link at features.interlockGroups.interlockGroups.href, appending a search term such as name=substring to select the interlocks, top if you expect lots of them, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link if there is one.

Finding the status of many items including an interlock group

  1. 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.
  2. Create a status subscription for those items.

Finding the status of an interlock group

  1. Find the href for the interlock using the process above, and GET it.
  2. Take the updates href from that page.
  3. GET it.
  4. Use the flag rules above to interpret the status flags you receive.
  5. Follow the next link to stay up to date.

Overriding an Interlock Group

  1. Find the href for the interlock using the process above.
  2. GET it.
  3. Find the API URL for the override you need (disable or re-enable) in the commands structure of the results.
  4. 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKInterlockGroupSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The site has neither the RESTStatus nor the RESTOverrides licence.

Authorization

API_keyapiKey in header

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

Search interlock groups
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/interlock_groups/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsquery

This 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

200OKInterlockGroupSummary & object

Success.

403Forbidden

The site has neither the RESTStatus nor the RESTOverrides licence.

404Not Found

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 header

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

Get details of an interlock group
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()
200
{
  "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.

POST
https://127.0.0.1:8904/api/interlock_groups/{id}/disable

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

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Disable an interlock group.
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.

POST
https://127.0.0.1:8904/api/interlock_groups/{id}/enable

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

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Re-enable an interlock group.
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.

GET
https://127.0.0.1:8904/api/interlock_groups/{id}/updates

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

idstringrequiredpath

An internal identifier.

fieldsstringstatusstatusTextstatusFlagsquery

This 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

200OKobject

Success. See the introduction for a description of the status fields.

403Forbidden

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.

404Not Found

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 header

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

Monitor an interlock group.
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()
200
{
  "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:

  1. 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, or type parameter. 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.

  2. POST to create a subscription. Your program should get the URL from items.updates.href in the results of GET /api.

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

  4. GET the next.href link 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 the next link.

  5. Goto 4.

Licensing

Every REST licence enables the items controller: RESTEvents, RESTCreateEvents, RESTCardholders, RESTStatus, and RESTOverrides.

Search items

GET
https://127.0.0.1:8904/api/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

namestringquery

Limits 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>query

Limits 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>query

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

typestringquery

Only 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>= 1100query

Sets the maximum number of items to return per page.

sortstringidname-id-namequery

Changes 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:

  1. 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.
  2. Following a next link 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.

fieldsstringhrefidnametypedivisionserverDisplayNamenotesdescriptionshortNamequery

Return 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

200OKItemSearch

Success

403Forbidden

The site does not have a REST licence.

Authorization

API_keyapiKey in header

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

Search items
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/items/{id}

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

idstringrequiredpath

The ID of the item.

fieldsstringhrefidnametypedivisionserverDisplayNamenotesdescriptionshortNamequery

Return 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

200OKItemSummary & object

Success

404Not Found

That is not the href of an item. At least not one that your operator has the privilege to view.

Authorization

API_keyapiKey in header

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

Get details of an item
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/items/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

200OKItemTypes

Success

403Forbidden

The site does not have a REST licence.

Authorization

API_keyapiKey in header

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

List item types
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()
200
{
  "itemTypes": [
    {
      "id": "1",
      "name": "Cardholder",
      "canonicalTypeName": "cardholder"
    },
    {
      "id": "2",
      "name": "Access Group",
      "canonicalTypeName": "accessgroup"
    }
  ]
}

Retrieve status updates

GET
https://127.0.0.1:8904/api/items/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:

  1. Create a subscription with a POST.
  2. Process the statuses in the results, if there are any.
  3. Wait a second or two to avoid tight loops.
  4. GET the link from the results. It may take up to a minute to respond.
  5. If 404, go to 1.
  6. 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

bookmarkstringrequiredquery

Identifies 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

200OKItemUpdate

Success

403Forbidden

The site does not have a REST licence.

404Not Found

The subscription does not exist, which probably means you waited too long between calls.

Authorization

API_keyapiKey in header

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

Retrieve status updates
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/items/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

application/json

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

Response

200OKItemUpdate

Success

400Bad Request

The server could not parse the request body. Check your JSON.

401Unauthorized

In versions prior to 9.30 it means the operator does not have privilege to view the monitored items.

403Forbidden

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 header

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

Subscribe to status updates
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()
Request Body
{
  "itemIds": [
    "508",
    "526"
  ]
}
200
{
  "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

  • offline means the item is configured with a keepalive time and it has not seen an API request in that time.

  • faulty means 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

PATCH
https://127.0.0.1:8904/api/items/{id}

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

application/json

The status text and fault condition of your API client.

Both fields are optional.

hasFaultboolean

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

customStatusTextstring

The 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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. The server has feedback for you in the body of its response.

204No Content

Success.

400Bad Request

The server could not make sense of your payload. Its response will contain feedback.

403Forbidden

You PATCHed the URL of a REST Client item, but not your REST Client item.

404Not Found

That is not the URL of a REST Client item your operator can see.

Authorization

API_keyapiKey in header

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

Set status
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()
Request Body
{
  "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:

  • quarantined means the locked cannot be allocated to a cardholder. It needs a clean, in other words.

  • allocated means it is allocated to a cardholder, so another cardholder cannot self-allocate it.

  • free means 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, or free.

  • Like a door, if and only if the locker is online, exactly one of the closed or open flags will be there and one of locked or unlocked.

Use cases

Displaying all banks and lockers, and the cardholders assigned to them

  1. GET /api
  2. Follow the link at features.lockerBanks.lockerBanks.href , adding a search term to the query to thin out the results.
  3. 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

  1. Find the href of the locker, as above. Normally one that does not have someone already allocated.

  2. Find the href of the cardholder using the cardholders API.

  3. PATCH the cardholder with the locker's href in a property called locker in an element of an array called lockers.add. Add from and until properties 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.

  1. GET /api
  2. Follow the link at features.lockerBanks.lockerBanks.href , adding ? or & as appropriate then fields=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.
  3. Dig through the results for your locker bank, then through the lockers array of that locker bank for your locker, then into the commands block of that locker for another block called open or quarantine depending on your need. If your operator is able to override that locker, that block will contain a field called href which gives the URL you need to POST to override the locker (open, for example).

Search locker banks

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKLockerBankSearch

Success.

403Forbidden

The installation lacks a lockers licence, or it is missing RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.

Authorization

API_keyapiKey in header

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

Search locker banks
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/locker_banks/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnameshortNamedescriptiondivisionnotesconnectedControllerlockerslockers.defaultslockers.connectedController...defaultsdefaultsquery

Sets 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

200OKLockerBankDetail

Success.

403Forbidden

The installation lacks a lockers licence, or it is missing RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.

404Not Found

Your REST operator does not have the privilege to view that locker bank.

Authorization

API_keyapiKey in header

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

Get details of a locker bank
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/lockers/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnameshortNamedescriptiondivisionassignmentsnotescommandsconnectedControllerdefaultsdefaultsquery

Sets 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

200OKLockerDetail

Success.

403Forbidden

The installation lacks a lockers licence, or it is missing both RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.

404Not Found

Your REST operator does not have the privilege to view that locker bank ('Locker assignments').

Authorization

API_keyapiKey in header

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

Get details of a locker
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/lockers/{id}/open

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will contain feedback from the server.

204No Content

Success.

403Forbidden

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.

404Not Found

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 header

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

Open a locker
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

POST
https://127.0.0.1:8904/api/lockers/{id}/quarantine

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. The response body will contain feedback from the server.

204No Content

Success.

400Bad Request

Check the response body for an error message. A common fault is the locker being allocated.

403Forbidden

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.

404Not Found

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 header

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

Quarantine a locker
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

POST
https://127.0.0.1:8904/api/lockers/{id}/cancel_quarantine

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server.

204No Content

Success.

403Forbidden

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.

404Not Found

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 header

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

Un-quarantine a locker
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 closed meaning that the macro is running.

Use cases

Listing Macros

  1. GET /api.
  2. 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.
  3. Process the results, following the next link until there isn't one.

Running a Macro

  1. Find the href for the macro using the process above.
  2. GET it.
  3. 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefidnamedescriptiondivisioncommandsnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKMacroSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search macros
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/macros/{id}

This returns the detail of one macro.

Follow the 'href' field in an macro summary to get here.

Parameters

idstringrequiredpath

The ID of the macro.

fieldsstringhrefidnamedescriptiondivisioncommandsnotesupdatesdefaultsquery

This 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

200OKMacroSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of a macro
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/macros/{id}/run

Sends a run request to a macro.

Parameters

idstringrequiredpath

The ID of the macro.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Run a macro
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.

  1. GET /api.

  2. Follow the link at features.operatorGroups.operatorGroups.href (adding search terms, and setting top high to save pagination). 3. Find your operator group.

  3. The link at cardholders returns the cardholders who have membership of your group.

  4. Get the link at cardholders (which is the same on the search results and the details page), look in that cardholder's operatorGroups array, 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnamedescriptiondivisionnotescardholdersdivisionsdefaultsdefaultsquery
posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKOperatorGroupSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search operator groups
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/operator_groups/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnamedescriptiondivisionnotescardholdersdivisionsdefaultsdefaultsquery

Sets 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

200OKOperatorGroupSummary & object

Success.

403Forbidden

The site does not have the RESTCardholders licence.

404Not Found

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 header

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

Get details of an operator group
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/operator_groups/{id}/cardholders

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>defaultscardholderhrefdefaultsquery

Specifies 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

200OKobject

Success.

403Forbidden

You do not have privileges to see the operator group. Check the body of the result for a description of the problem.

404Not Found

The ID is invalid, or it is valid but you do not have privileges to see the operator group.

Authorization

API_keyapiKey in header

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

Get membership of an operator group
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()
200
{
  "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

  1. 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.
  2. 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:

  • relayStateUnknown means the controller does not know what the output should be doing.

  • closed means the output relay is closed.

  • open means the output relay is open.

  • pulsed means the relay's change in state is momentary.

  • switchingDisabled means switching this output is disabled.

  • overridden can 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

  1. GET /api.
  2. Follow the link at features.outputs.outputs.href , appending a search term such as name=substring to select the outputs, and fields to tell the server what to return about each. The next section covers those query parameters.
  3. Process the results, following the next link until there isn't one.

Switching an Output

  1. Find the href for the output using the process above.
  2. GET it.
  3. Look in the commands structure of the results to find the API URLs that turn the output on, off, or cancel a previous override. Use the until variants if you want to specify an end time.
  4. POST to that URL. Those with until in their command block keys require a JSON object in the body; the others expect it empty.

Finding an output's status

  1. Find the href for the output using the process above, and GET it.
  2. Take the updates href from that page.
  3. GET it.
  4. Use the flag rules above to interpret the status flags you receive.
  5. Follow the next link 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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKOutputSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

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 header

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

Search outputs
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/outputs/{id}

This returns the detail of one output.

Follow the 'href' field in an output summary to get here.

Parameters

idstringrequiredpath

An internal identifier.

fieldsstringhrefidnameshortNamedescriptiondivisioncommandsconnectedControllerstatusFlagsstatusTextstatusnotesupdatesdefaultsquery

This 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

200OKOutputSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of an output
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/outputs/{id}/on

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

application/json

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

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Turn on an output
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Pulse an output

POST
https://127.0.0.1:8904/api/outputs/{id}/pulse

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

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The output is not configured for pulsing (8.70 and later only).

403Forbidden

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 header

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

Pulse an output
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

POST
https://127.0.0.1:8904/api/outputs/{id}/off

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

application/json

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

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

400Bad Request

The server could not parse the POST parameters. There could be a syntax error in your JSON.

403Forbidden

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 header

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

Turn off an output
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()
Request Body
{
  "endTime": "2018-07-31T00:00:00Z"
}

Cancel an override

POST
https://127.0.0.1:8904/api/outputs/{id}/cancel

Cancels an override, returning the output to its previous state.

Parameters

idstringrequiredpath

An internal identifier.

requested_bystringquery

Attributes 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

204No Content

Success.

403Forbidden

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 header

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

Cancel an override
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

GET
https://127.0.0.1:8904/api/outputs/{id}/updates

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

idstringrequiredpath

An internal identifier.

fieldsstringstatusstatusTextstatusFlagsquery

This 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

200OKobject

Success. See the introduction for a description of the three status fields.

403Forbidden

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.

404Not Found

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 header

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

Monitor an output
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()
200
{
  "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.

  1. GET the cardholder's access groups from the cardholder's detail page, or the search page with accessGroups added to the query's fields parameter.
  2. Iterate through the hrefs of those access groups, GETting each. That will return their detail pages.
  3. For each access group detail page, record the names and hrefs from the personalDataDefinitions block and add the parent.href link 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

  1. GET /api
  2. Follow the link at features.personalDataFields.personalDataFields.href , adding a search term to the query to thin out the results. For example, add name="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

GET
https://127.0.0.1:8904/api/personal_data_fields

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

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefidnamedescriptiondivisionserverDisplayNametypedefaultrequireduniquesortPriorityaccessGroupsregexregexDescriptiondefaultAccessoperatorAccessnotificationDefaultdefaultsdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKPDFDefinitionSearch

Success.

403Forbidden

The site does not have the RESTCardholders or RESTEvents licence.

Authorization

API_keyapiKey in header

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

Search PDF definitions
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()
200
{
  "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]

POST
https://127.0.0.1:8904/api/personal_data_fields

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

application/json

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 only

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

namestring
serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this PDF definition. Required when creating one.

typestringstringimagestrEnumnumericdateaddressphoneemailmobile

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

defaultstring

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

requiredbooleanfalse

If true, every cardholder with this PDF must have a value for it. No blanks allowed.

uniquebooleanfalse

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

defaultAccessstringnoAccessreadOnlyfullAccessfullAccess

This 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 only

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

sortPriorityinteger

This 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 only

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

Show child attributes
namestring
hrefstring<uri-reference>
notificationDefaultboolean

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

regexstring

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

regexDescriptionstring

Regular expressions often need explaining.

imageWidthinteger

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

imageHeightinteger

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

imageFormatstringbmpjpgpngdeprecated

Whether 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/png

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

isProfileImageboolean

True 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

201Created

Success.

400Bad Request

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.

403Forbidden

The operator does not have a privilege that allows creating PDFs or the server does not have the 'RESTConfiguration' licence.

Authorization

API_keyapiKey in header

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

Create [coming]
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/personal_data_fields/{id}

This returns details for a PDF definition. Follow the href in the summary to get here, rather than building it yourself.

Parameters

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefidnamedescriptiondivisionserverDisplayNametypedefaultrequireduniquesortPriorityaccessGroupsregexregexDescriptiondefaultAccessoperatorAccessnotificationDefaultdefaultsdefaultsquery

Specifies 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

200OKPDFDefinition

Success.

403Forbidden

The site does not have the RESTCardholders or RESTEvents licence

404Not Found

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 header

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

Get details of a PDF definition
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()
200
{
  "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]

DELETE
https://127.0.0.1:8904/api/personal_data_fields/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

400Bad Request

Deleting failed. This happens when the PDF is in use.

403Forbidden

The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.

404Not Found

That is not the URL of a PDF, or the operator is not privileged to view it.

Authorization

API_keyapiKey in header

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

Remove [coming]
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]

PATCH
https://127.0.0.1:8904/api/personal_data_fields/{id}

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

application/json

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 only

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

namestring
serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this PDF definition. Required when creating one.

typestringstringimagestrEnumnumericdateaddressphoneemailmobile

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

defaultstring

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

requiredbooleanfalse

If true, every cardholder with this PDF must have a value for it. No blanks allowed.

uniquebooleanfalse

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

defaultAccessstringnoAccessreadOnlyfullAccessfullAccess

This 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 only

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

sortPriorityinteger

This 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 only

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

Show child attributes
namestring
hrefstring<uri-reference>
notificationDefaultboolean

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

regexstring

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

regexDescriptionstring

Regular expressions often need explaining.

imageWidthinteger

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

imageHeightinteger

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

imageFormatstringbmpjpgpngdeprecated

Whether 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/png

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

isProfileImageboolean

True 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

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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.

409Conflict

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 header

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

Update [coming]
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()
Request Body
{
  "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:

  1. GET /api.
  2. If running 8.00 or earlier, follow the link at features.cardTypes.cardTypes.href (which will be to /api/card_types), or
  3. 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).
  4. Iterate through the array to find the element with credentialClass: piv, and
  5. 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 fields parameter.

Create a cardholder with a PIV card

POST
https://127.0.0.1:8904/api/cardholders [PIV]

This shows how to create a cardholder with a PIV card.

Body

application/json

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.

firstNamestring

You must set either a first or last name when creating a cardholder.

divisionobjectrequired

You must set a division when creating a cardholder.

Show child attributes
hrefstring<uri-reference>
cardsArray<NewPIVCard>

An array of cards to give to the new cardholder.

Show child attributes
typeobjectrequired
Show child attributes
hrefstring<uri-reference>

The href of the server's PIV card type. This documentation has a section called "finding the PIV card type" if you need it.

statusobject
Show child attributes
valuestring
numberstringrequired

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

pivDataobjectrequired

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

Show child attributes
chuidCHUIDrequired

Required when creating a card, ignored when updating one.

The FASC-N in the chuid block, and its relationship with the card number, is the only difference between PIV and PIV-I cards in Command Centre.

Show child attributes
hashstringrequired

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

fascnstringrequired

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

orgIdentifierstring

Optional.

dunsstring

Optional.

pivStatusobject

The PIV status held in the type field in this block is distinct from the generic card status, which is a different field outside the pivData block.

It is optional. If you omit it when creating a card, the card will be enabled with a status of "NotChecked", which—despite how it sounds—is a perfectly valid operating status. If Command Centre is doing periodic certificate validation, the status will eventually change (to 'normal', all going well).

You should send this block when creating a card only if you are doing your own certificate validation.

When updating an existing card, this is the only field it makes sense to send (specifically, the type field inside it).

type must be set to one of these values, based on the result of your validation:

  • Normal
  • Offline
  • Revoked
  • Expired
  • CertInChainRevoked
  • CertInChainExpired
  • IssuerSigCertRevoked
  • IssuerSigCertExpired
  • NotTrusted
  • PolicyError
  • OtherError
  • NotChecked

If you set it to any value except 'NotChecked, 'Normal', or 'Offline', Command Centre will consider the card invalid and deactivate it regardless of what you pass as the card status. You, or Command Centre's periodic certificate validation, may validate the card again later.

Show child attributes
typestringNormalOfflineRevokedExpiredCertInChainRevokedCertInChainExpiredIssuerSigCertRevokedIssuerSigCertExpiredNotTrustedPolicyErrorOtherErrorNotChecked
contentSigningCertstringrequired

This is required when creating a PIV or PIV-I card. The API will reject your request if this is missing or not a Base64-encoded certificate, but it will not validate the certificate itself.

This example is shortened to fit on screen. Real certificates are at least a thousand characters.

cardAuthenticationCertstring

This contains the CAK, which is necessary for the secure use of contactless cards. It is not required for contact cards.

Optional when creating a PIV or PIV-I card. The API will reject your request if this is present and not a Base64-encoded certificate, but it will not validate the certificate itself.

pivAuthenticationCertstringrequired

This is required when creating a PIV or PIV-I card. The API will reject your request if this is missing or not a Base64-encoded certificate, but it will not validate the certificate itself.

fingerprintsstring

If you send this when creating a card, it should be the cardholder's fingerprints contained in the card's Cardholder Fingerprints data object with the error detection code removed: the whole CBEFF structure including the CBEFF_HEADER, CBEFF_BIOMETRIC_RECORD, and CBEFF_SIGNATURE_BLOCK components. Refer to Section 9 of NIST Special Publication 800-76-2: Biometric Data Specification for Personal Identity Verification.

It is optional. The API will check that this is Base64, but will not verify that it is valid biometric data.

Response

201Created

Success.

400Bad Request

The body of the POST did not describe a valid cardholder.

Authorization

API_keyapiKey in header

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

Create a cardholder with a PIV card
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()
Request Body

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

GET
https://127.0.0.1:8904/api/cardholders/{id} [PIV]

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

idstringrequiredpath

An internal identifier.

Response

200OKobject

Success.

404Not Found

The operator does not have a privilege that allows reading cardholders, or there is no cardholder at that address.

Authorization

API_keyapiKey in header

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

Get details of a cardholder
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()
200
{
  "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

PATCH
https://127.0.0.1:8904/api/cardholders/{id} [PIV]

This shows how to update a cardholder's PIV cards.

Body

application/json

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.

cardsobject
Show child attributes
addArray<NewPIVCard>

An array of new cards to give to the cardholder.

Show child attributes
typeobjectrequired
Show child attributes
hrefstring<uri-reference>

The href of the server's PIV card type. This documentation has a section called "finding the PIV card type" if you need it.

statusobject
Show child attributes
valuestring
numberstringrequired

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

pivDataobjectrequired

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

Show child attributes
chuidCHUIDrequired

Required when creating a card, ignored when updating one.

The FASC-N in the chuid block, and its relationship with the card number, is the only difference between PIV and PIV-I cards in Command Centre.

Show child attributes
hashstringrequired

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

fascnstringrequired

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

orgIdentifierstring

Optional.

dunsstring

Optional.

pivStatusobject

The PIV status held in the type field in this block is distinct from the generic card status, which is a different field outside the pivData block.

It is optional. If you omit it when creating a card, the card will be enabled with a status of "NotChecked", which—despite how it sounds—is a perfectly valid operating status. If Command Centre is doing periodic certificate validation, the status will eventually change (to 'normal', all going well).

You should send this block when creating a card only if you are doing your own certificate validation.

When updating an existing card, this is the only field it makes sense to send (specifically, the type field inside it).

type must be set to one of these values, based on the result of your validation:

  • Normal
  • Offline
  • Revoked
  • Expired
  • CertInChainRevoked
  • CertInChainExpired
  • IssuerSigCertRevoked
  • IssuerSigCertExpired
  • NotTrusted
  • PolicyError
  • OtherError
  • NotChecked

If you set it to any value except 'NotChecked, 'Normal', or 'Offline', Command Centre will consider the card invalid and deactivate it regardless of what you pass as the card status. You, or Command Centre's periodic certificate validation, may validate the card again later.

Show child attributes
typestringNormalOfflineRevokedExpiredCertInChainRevokedCertInChainExpiredIssuerSigCertRevokedIssuerSigCertExpiredNotTrustedPolicyErrorOtherErrorNotChecked
contentSigningCertstringrequired

This is required when creating a PIV or PIV-I card. The API will reject your request if this is missing or not a Base64-encoded certificate, but it will not validate the certificate itself.

This example is shortened to fit on screen. Real certificates are at least a thousand characters.

cardAuthenticationCertstring

This contains the CAK, which is necessary for the secure use of contactless cards. It is not required for contact cards.

Optional when creating a PIV or PIV-I card. The API will reject your request if this is present and not a Base64-encoded certificate, but it will not validate the certificate itself.

pivAuthenticationCertstringrequired

This is required when creating a PIV or PIV-I card. The API will reject your request if this is missing or not a Base64-encoded certificate, but it will not validate the certificate itself.

fingerprintsstring

If you send this when creating a card, it should be the cardholder's fingerprints contained in the card's Cardholder Fingerprints data object with the error detection code removed: the whole CBEFF structure including the CBEFF_HEADER, CBEFF_BIOMETRIC_RECORD, and CBEFF_SIGNATURE_BLOCK components. Refer to Section 9 of NIST Special Publication 800-76-2: Biometric Data Specification for Personal Identity Verification.

It is optional. The API will check that this is Base64, but will not verify that it is valid biometric data.

updateArray<object>
Show child attributes
hrefstring<uri-reference>required
statusobject
Show child attributes
valuestring
pivDataobject

The only field you can change in this block is pivStatus.type.

Show child attributes
pivStatusobject
Show child attributes
typestring
removeArray<object>

To remove a card you must send its href, and you may send a new status.

Show child attributes
hrefstring<uri-reference>required
statusobject
Show child attributes
valuestring

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will add feedback from the server about your PATCH.

204No Content

Success, with no feedback.

400Bad Request

The parameters are invalid, or other errors prevented the update.

Authorization

API_keyapiKey in header

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

Update a cardholder's PIV cards
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()
Request Body

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

  1. GET /api
  2. Follow the link at features.receptions.receptions.href after adding search terms such as name="Front lobby". A site typically has very few receptions, so if you add top=1000 you're very unlikely to need to follow a next link.
  3. Find the reception you're after and use its href in a visit.

Search receptions

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefnamedescriptiondivisionserverDisplayNamedefaultVisitorTypenotesdefaultsdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKReceptionSearch

Success.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search receptions
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/receptions/{id}

This returns details for a reception. Follow the href in the summary to get here, rather than building it yourself.

Parameters

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefnamedescriptiondivisionserverDisplayNamedefaultVisitorTypenotesdefaultsdefaultsquery

Specifies 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

200OKReception

Success.

403Forbidden

The site does not have the RESTCardholders or RESTEvents licence

404Not Found

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 header

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

Get details of a reception
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()
200
{
  "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

GET
https://127.0.0.1:8904/api/cardholders/redactions

This returns all cardholders' redactions. Paginated.

Parameters

cardholderstringquery

Limits the results to redactions to cardholders with these IDs. Separate with commas.

statusstringpendinginProgresscancelleddonefailedquery

Limits the results to redactions with this status. Separate with commas.

typestringnormalEventscardholderquery

Limits the results to redactions of this type. Separate with commas.

afterstring<date-time>query

Limits the results to redactions that were scheduled to occur after this time.

beforestring<date-time>query

Limits the results to redactions that were scheduled to occur before this time.

fieldsArray<string>before cardholder finishTime href redactionOperator status type whenquery

Specifies the fields you want in the results.

Treat the string matches as case sensitive: use lastName rather than lastname.

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

Response

200OKobject

An array of redactions, and a link to the next page if any.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search redactions
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/cardholders/redactions

Schedules a cardholder redaction.

Body

application/json

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.

cardholderobject

The href of the cardholder whose events or item this redaction should affect.

Required.

typestringnormalEventscardholder

Whether 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

201Created

Success.

400Bad Request

The cardholder does not exist.

403Forbidden

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 header

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

Schedule a redaction
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()
Request Body
{
  "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

DELETE
https://127.0.0.1:8904/api/cardholders/redactions

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

201Created

Success.

403Forbidden

The operator does not have permissions to redact or to delete a cardholder, or the server is not licensed for cardholder operations.

404Not Found

No such redaction, or your operator cannot see it.

Authorization

API_keyapiKey in header

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

Cancel a redaction
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

  1. GET /api
  2. Follow the link at features.roles.roles.href after adding search terms such as name="supervisor".
  3. Find the href of the role with which you want to link your two cardholders.
  4. Find the href of the cardholder who will perform this role (the supervisor).
  5. Find the href of the cardholder for whom he or she will perform this role (the supervised).
  6. 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

GET
https://127.0.0.1:8904/api/roles

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

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKobject

Success.

403Forbidden

The site does not have the RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search
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()
200
{
  "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]

POST
https://127.0.0.1:8904/api/roles

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

application/json

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.

namestring
hrefstring<uri-reference>read only

This is the string to use when creating a relationship between cardholders using this role.

serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this role. Required in a POST, optional in a PATCH.

enableCompetencyExpiryWarningsboolean

Controls whether CC sends notifications to a person holding this role when their staff's competencies are about to expire.

enableCardExpiryWarningsboolean

Controls whether CC sends notifications to a person holding this role when their staff's cards are about to expire.

Response

201Created

Success.

400Bad Request

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.

403Forbidden

The operator does not have a privilege that allows creating roles, or the server does not have the 'RESTConfiguration' licence.

Authorization

API_keyapiKey in header

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

Create [coming]
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()
Request Body
{
  "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]

DELETE
https://127.0.0.1:8904/api/roles/{id}

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

idstringrequiredpath

An internal identifier.

Response

200OK

Success.

204No Content

Success.

400Bad Request

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.

403Forbidden

The operator has the permission to view the item but not delete it, or the server does not have the 'RESTConfiguration' licence.

404Not Found

That is not the URL of a role, or the operator is not privileged to view it.

Authorization

API_keyapiKey in header

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

Remove [coming]
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]

PATCH
https://127.0.0.1:8904/api/roles/{id}

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

application/json

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.

namestring
hrefstring<uri-reference>read only

This is the string to use when creating a relationship between cardholders using this role.

serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this role. Required in a POST, optional in a PATCH.

enableCompetencyExpiryWarningsboolean

Controls whether CC sends notifications to a person holding this role when their staff's competencies are about to expire.

enableCardExpiryWarningsboolean

Controls whether CC sends notifications to a person holding this role when their staff's cards are about to expire.

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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.

409Conflict

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 header

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

Update [coming]
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

Limits 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>hrefnamedescriptiondivisionnotestypedayCategoriesdefaultsquery

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKScheduleSearch

Success. See the note in the description about privileges if your result set is empty.

403Forbidden

The site does not have the RESTStatus, RESTOverrides, or RESTCardholders licence.

Authorization

API_keyapiKey in header

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

Search schedules
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/schedules

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

application/json

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.

namestring
descriptionstring
divisionobject

The division containing this schedule. It is possible to change a schedule's division after creating it.

Show child attributes
hrefstring<uri-reference>
notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

typeobject

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

Show child attributes
typestringaccessZoneScheduleaccessSchedulealarmZoneScheduleoutputSchedulenotificationSchedulehVLFScheduleelevatorKioskControlSchedule
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 time field of each object inside times must be a string of the form HH:MM between 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 grant and deny. 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, or free. The extra flag usePin means 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, and user2. Use the words user1 and user2 here even if you have renamed them in the server properties.

  • An Output Schedule can be on or off, 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 notificationEnabled and notificationDisabled.

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

Show child attributes
dayCategoryobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

timesArray<object>
Show child attributes
timestring
stateArray<string>
scheduledItemsArray<object>read only

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

Show child attributes
hrefstring<uri-reference>
namestring

Response

201Created

Success.

400Bad Request

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.

403Forbidden

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 header

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

Create a schedule
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/schedules/{id}

This returns the detail of one schedule.

Follow the 'href' field in an schedule summary to get here.

Parameters

idstringrequiredpath

The ID of the schedule.

fieldsstringhrefnamedescriptiondivisionnotestypedayCategoriesquery

This 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

200OKScheduleSummary & object

Success.

403Forbidden

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.

404Not Found

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 header

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

Get details of a schedule
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()
200
{
  "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

DELETE
https://127.0.0.1:8904/api/schedules/{id}

Deletes the schedule identified by the request's URL.

Added in 8.50.

Parameters

idstringrequiredpath

The ID of the schedule. Do not add it yourself: you will receive this URL from other API calls.

Response

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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 header

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

Delete a schedule
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

PATCH
https://127.0.0.1:8904/api/schedules/{id}

Modifies a schedule according to the body of the PATCH.

Added in 8.50.

Body

application/json

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.

namestring
descriptionstring
divisionobject

The division containing this schedule. It is possible to change a schedule's division after creating it.

Show child attributes
hrefstring<uri-reference>
notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

typeobject

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

Show child attributes
typestringaccessZoneScheduleaccessSchedulealarmZoneScheduleoutputSchedulenotificationSchedulehVLFScheduleelevatorKioskControlSchedule
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 time field of each object inside times must be a string of the form HH:MM between 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 grant and deny. 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, or free. The extra flag usePin means 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, and user2. Use the words user1 and user2 here even if you have renamed them in the server properties.

  • An Output Schedule can be on or off, 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 notificationEnabled and notificationDisabled.

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

Show child attributes
dayCategoryobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

timesArray<object>
Show child attributes
timestring
stateArray<string>
scheduledItemsArray<object>read only

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

Show child attributes
hrefstring<uri-reference>
namestring

Parameters

idstringrequiredpath

The ID of the schedule. Do not add it yourself: you will receive this URL from other API calls.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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 header

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

Modify a schedule
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()
Request Body
{
  "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

  1. GET /api
  2. Follow the link at features.visits.visits.href after adding search terms such as name=Ginger Greeter. A site typically has many visits, so add top=1000 for efficiency's sake.
  3. Find the visit you're after. You can then modify it by PATCHing its href.

Use case: creating a visit

  1. Find the href of the reception at which your visitors will arrive.
  2. Look at the visitor management configuration for your reception's division.
  3. 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.
  4. Pick (or create) at least one cardholder for your visitor or visitors.
  5. Build a JSON payload containing all that.
  6. POST to the link at features.visits.visits.href (in the payload of the GET /api you did for the first step).

Search visits

GET
https://127.0.0.1:8904/api/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-namequery

Changes 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:

  1. 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.
  2. Following a next link 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>= 1query

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

namestringquery

Limits 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>query

Limits 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>query

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

descriptionstringquery

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

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

posintegerquery

Reserved for internal use. You may see it in URLs you receive from the server, but you must never add it yourself.

skipintegerquery

Reserved for internal use. Do not add it to your queries.

Response

200OKany

Success.

403Forbidden

The site does not have both the RESTCardholders and VisitorManagement licences.

Authorization

API_keyapiKey in header

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

Search visits
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()
200
{
  "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

POST
https://127.0.0.1:8904/api/visits

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

application/json

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.

namestringrequired

You 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!

descriptionstring

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

receptionobjectrequired

Every 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"

Show child attributes
hrefstring<uri-reference>
visitorTypeobjectrequired

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

Show child attributes
hrefstring<uri-reference>
hostobjectrequired

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

Show child attributes
hrefstring<uri-reference>
fromstring<date-time>required

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

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.

locationstring

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

Show child attributes
hrefstring<uri-reference>
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.

Show child attributes
hrefstring<uri-reference>

Response

201Created

Success.

400Bad Request

The body of the POST did not describe a valid visit. Check the body of the server's response for hints.

403Forbidden

The site does not have both the RESTCardholders and VisitorManagement licences.

Authorization

API_keyapiKey in header

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

Create a visit
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()
Request Body
{
  "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

GET
https://127.0.0.1:8904/api/visits/{id}

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

idstringrequiredpath

An internal identifier.

fieldsArray<string>hrefnamedescriptiondivisionserverDisplayNamenotesreceptionvisitorTypehostfromuntillocationbadgeTextvisitorAccessGroupsvisitorsvisitors.cardholdervisitors.hrefvisitors.invitationvisitors.status...defaultsdefaultsquery

Specifies 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

200OKVisit

Success.

403Forbidden

The site does not have both the RESTCardholders and VisitorManagement licences.

404Not Found

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 header

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

Get details of a visit
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()
200
{
  "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

PATCH
https://127.0.0.1:8904/api/visits/{id}

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

application/json

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.

namestring

You 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!

descriptionstring

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

receptionobject

Every 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"

Show child attributes
hrefstring<uri-reference>
visitorTypeobject

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

Show child attributes
hrefstring<uri-reference>
hostobject

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

Show child attributes
hrefstring<uri-reference>
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.

locationstring

Free text. Entirely optional in the POST and PATCH.

visitorAccessGroupsobject

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.

Show child attributes
addArray<object>
Show child attributes
hrefstring<uri-reference>
removeArray<object>
Show child attributes
hrefstring<uri-reference>
visitorsobject

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.

Show child attributes
addArray<object>
Show child attributes
hrefstring<uri-reference>
removeArray<object>
Show child attributes
hrefstring<uri-reference>

Parameters

idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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 header

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

Modify a visit
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()
Request Body
{
  "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

PATCH
https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}

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

application/json

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.

statusobject

This is the same status that you see in a visit GET for each visitor except that you only need to send the value field.

Show child attributes
valuestringexpectedsigningInsignedInonSiteexpectedBackdepartedcancelled

An enum describing the state in which you want your visitor to be.

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

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.

403Forbidden

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.

404Not Found

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 header

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

Modify a visitor's status
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()
Request Body
{
  "status": {
    "value": "signingIn"
  }
}

Notify that a visitor is signing in

POST
https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_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

application/json

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.

emailSubjectstring

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

emailMessagestring

If you want your email notification to contain body text, put it here.

smsMessagestring

If you wish to send an SMS notification (a TXT), put it here. Leave it blank if you only wish to send email.

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

The body of the PATCH did not describe a notification. Check the body of the server's response for hints.

403Forbidden

The site does not have the RESTCardholders and VisitorManagement licences, or the operator does not have sufficient privileges, or the visit has expired.

404Not Found

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 header

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

Notify that a visitor is signing in
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()
Request Body
{
  "emailSubject": "Visitor arriving",
  "emailMessage": "",
  "smsMessage": "Visitor arriving"
}

Notify that a visitor is on-site

POST
https://127.0.0.1:8904/api/visits/{id}/visitors/{secondary_id}/notify_onsite

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

application/json

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.

emailSubjectstring

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

emailMessagestring

If you want your email notification to contain body text, put it here.

smsMessagestring

If you wish to send an SMS notification (a TXT), put it here. Leave it blank if you only wish to send email.

Parameters

idstringrequiredpath

An internal identifier.

secondary_idstringrequiredpath

An internal identifier.

Response

200OK

Success. Future versions will return feedback from the server about your PATCH.

204No Content

Success.

400Bad Request

The body of the PATCH did not describe a notification. Check the body of the server's response for hints.

403Forbidden

The site does not have the RESTCardholders and VisitorManagement licences, or the operator does not have sufficient privileges, or the visit has expired.

404Not Found

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 header

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

Notify that a visitor is on-site
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()
Request Body
{
  "emailSubject": "Visitor arriving",
  "emailMessage": "",
  "smsMessage": "Visitor arriving"
}

Models

AccessGroupCommonFields

object

These are the fields common to the access group summary, details, POST, and PATCH.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

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

Example
{
  "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

object

These are the fields in the access group summary.

These are the fields common to the access group summary, details, POST, and PATCH.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

A 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 only

An alphanumeric identifier for this access group. No API calls use access group IDs.

hrefstring<uri-reference>read only

A link to an access group detail object for this access group.

notesstring
cardholdersobjectread only

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

Show child attributes
hrefstring<uri-reference>
serverDisplayNamestringread only

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

Example
{
  "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

object

These are the fields common to the POST and PATCH.

These are the fields common to the access group summary, details, POST, and PATCH.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

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

notesstring
parentobject

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

membershipAutoRemoveExpiredboolean

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

Example
{
  "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

object

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>
Show child attributes
namestringread only

Read-only.

hrefstring<uri-reference>

The href of the PDF definition.

Example
{
  "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

object

An array of access group summaries, described in the next section, and a next link for more.

resultsArray<AccessGroupSummary>

An array of access group summaries.

Show child attributes

These are the fields in the access group summary.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

A 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 only

An alphanumeric identifier for this access group. No API calls use access group IDs.

hrefstring<uri-reference>read only

A link to an access group detail object for this access group.

notesstring
cardholdersobjectread only

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

Show child attributes
hrefstring<uri-reference>
serverDisplayNamestringread only

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

nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

namestring

All items have a name. Command Centre makes one up for you if you omit it when creating an item.

descriptionstring
divisionobject

Mandatory when creating any access group. In this example, we want the access group in division 352.

parentobject

A 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 only

An alphanumeric identifier for this access group. No API calls use access group IDs.

hrefstring<uri-reference>read only

A link to an access group detail object for this access group.

notesstring
cardholdersobjectread only

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

Show child attributes
hrefstring<uri-reference>
serverDisplayNamestringread only

If 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>
Show child attributes
namestringread only

Read-only.

hrefstring<uri-reference>

The href of the PDF definition.

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.

Show child attributes
hrefstring<uri-reference>

The href of the child access group.

namestring

The name of the child access group.

visitorboolean

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

escortVisitorsboolean

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

lockUnlockAccessZonesboolean

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

enterDuringLockdownboolean

If true, members of this group are not subject to lockdown restrictions when requesting to enter its access zones.

New to 8.40.

firstCardUnlockboolean

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

overrideAperioPrivacyboolean

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

aperioOfflineAccessboolean

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

disarmAlarmZonesboolean

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

armAlarmZonesboolean

If true, members of this group can use a reader or terminal to arm the group's access zones' alarm zones.

New to 8.40.

hvLfFenceZonesboolean

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

viewAlarmsboolean

If true, members of this group can view alarms and inputs on remote arming terminals ("RATs") and HBUS terminals.

New to 8.40.

shuntboolean

If true, members of this group can shunt (isolate) items using RATs and HBUS terminals.

New to 8.40.

lockOutFenceZonesboolean

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

cancelFenceZoneLockoutboolean

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

ackAllboolean

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

ackBelowHighboolean

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

selectAlarmZoneboolean

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

armWhileAlarmboolean

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

armWhileActiveAlarmboolean

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

isolateAlarmZonesboolean

Members 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 only

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

Show child attributes
accessZoneobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

scheduleobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

saltoAccessArray<object>read only

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

Show child attributes
saltoItemTypeobject

This block tells you whether the Salto item is a Salto door or a Salto 'access zone'.

Show child attributes
valuestringsaltoAccessZonesaltoDoor
saltoItemany

The name and href of the Salto zone or Salto door to which this access group gives access.

scheduleobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

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.

Show child attributes
alarmZoneobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

Example
{
  "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

object

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

cardholderobject

The name and href of the member cardholder.

fromstring<time-stamp>
untilstring<time-stamp>
Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this Access Zone.

doorsArray<object>

A list containing names of and links to the doors that control entry to this access zone.

Show child attributes
namestring
hrefstring<uri-reference>

This is the link to the door's detail, which contains a return link back to this page in either its entryAccessZone or exitAccessZone field.

zoneCountinteger

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

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

commandsobject

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

Show child attributes
freeobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'free - no PIN' access mode until the next scheduled change.

freeUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'free - no PIN' access mode for a fixed time.

freePinobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'free - PIN' access mode until the next scheduled change.

freePinUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'free - PIN' access mode for a fixed time.

secureobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'secure - no PIN' access mode until the next scheduled change.

secureUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'secure - no PIN' access mode for a fixed time.

securePinobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'secure - PIN' access mode until the next scheduled change.

securePinUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'secure - PIN' access mode for a fixed time.

codeOnlyobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'code or card - no PIN' access mode until the next scheduled change.

codeOnlyUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'code or card - no PIN' access mode for a fixed time.

codeOnlyPinobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'code or card - PIN' access mode until the next scheduled change.

codeOnlyPinUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'code or card - PIN' access mode for a fixed time.

dualAuthobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'dual auth - no PIN' access mode until the next scheduled change.

dualAuthUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'dual auth - no PIN' access mode for a fixed time.

dualAuthPinobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'dual auth - PIN' access mode until the next scheduled change.

dualAuthPinUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to override the zone into 'dual auth - PIN' access mode for a fixed time.

forgiveAntiPassbackobject
Show child attributes
hrefstring<uri-reference>

POST to this to forgive anti-passback for all cardholders in the zone.

setZoneCountobject
Show child attributes
hrefstring<uri-reference>

POST to this set the zone's cardholder count.

lockDownobject
Show child attributes
hrefstring<uri-reference>

POST to this set the zone into lockdown mode.

cancelLockDownobject
Show child attributes
hrefstring<uri-reference>

POST to this cancel lockdown on the zone, returning it to its scheduled mode.

cancelobject
Show child attributes
hrefstring<uri-reference>

POST to this cancel an active override on the zone, returning it to its scheduled mode. This will not affect a lockdown.

Example
{
  "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

object

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

Show child attributes
hrefstring<uri-reference>

A link to the details of this alarm.

idstring

An alphanumeric identifier for this alarm, unique to the server.

timestring

The time the alarm occurred.

messagestring

The alarm's message.

sourceobject

The ID and href are new to 8.10.

Show child attributes
idstring

The alphanumeric ID of the alarm's source item.

namestring

The name of the alarm's source item at the time of the event.

hrefstring<uri-reference>

Link to the source item.

typestring

The name of this alarm's event type.

Deprecated in favour of the eventType block.

eventTypeobject

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

statestringunacknowledgedacknowledgedprocessed

The state of the alarm. Alarms start at "unacknowledged".

activeboolean

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

divisionobject

The division entity representing the division of the alarm. GET the href for full details.

Show child attributes
hrefstring<uri-reference>
eventobject

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

Show child attributes
hrefstring<uri-reference>

Link to the event page for this alarm. GET it for the fields found on an event that are not on an alarm.

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.

viewobject

POST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.

Show child attributes
hrefstring<uri-reference>
commentobject

POST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.

Show child attributes
hrefstring<uri-reference>
acknowledgeobject

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

Show child attributes
hrefstring<uri-reference>
acknowledgeWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
processobject

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

Show child attributes
hrefstring<uri-reference>
processWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
forceProcessobject

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

Show child attributes
hrefstring<uri-reference>
nextobject

The link to the next page of alarms. Missing if you have retrieved all the current alarms.

Show child attributes
hrefstring
updatesobject

The link to follow to long-poll for alarm changes. Missing if you have not yet retrieved all the current alarms.

Show child attributes
hrefstring
Example
{
  "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

object
updatesArray<AlarmSummary>

An array of summaries of alarms created or modified since the previous call.

Show child attributes
hrefstring<uri-reference>

A link to the details of this alarm.

idstring

An alphanumeric identifier for this alarm, unique to the server.

timestring

The time the alarm occurred.

messagestring

The alarm's message.

sourceobject

The ID and href are new to 8.10.

Show child attributes
idstring

The alphanumeric ID of the alarm's source item.

namestring

The name of the alarm's source item at the time of the event.

hrefstring<uri-reference>

Link to the source item.

typestring

The name of this alarm's event type.

Deprecated in favour of the eventType block.

eventTypeobject

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

statestringunacknowledgedacknowledgedprocessed

The state of the alarm. Alarms start at "unacknowledged".

activeboolean

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

divisionobject

The division entity representing the division of the alarm. GET the href for full details.

Show child attributes
hrefstring<uri-reference>
eventobject

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

Show child attributes
hrefstring<uri-reference>

Link to the event page for this alarm. GET it for the fields found on an event that are not on an alarm.

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.

viewobject

POST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.

Show child attributes
hrefstring<uri-reference>
commentobject

POST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.

Show child attributes
hrefstring<uri-reference>
acknowledgeobject

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

Show child attributes
hrefstring<uri-reference>
acknowledgeWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
processobject

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

Show child attributes
hrefstring<uri-reference>
processWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
forceProcessobject

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

Show child attributes
hrefstring<uri-reference>
nextobject

Follow this link to perform another long poll.

Show child attributes
hrefstring
Example
{
  "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.

idstring

An alphanumeric identifier for this alarm, unique to the server.

timestring

The time the alarm occurred.

messagestring

The alarm's message.

sourceobject

The ID and href are new to 8.10.

Show child attributes
idstring

The alphanumeric ID of the alarm's source item.

namestring

The name of the alarm's source item at the time of the event.

hrefstring<uri-reference>

Link to the source item.

typestring

The name of this alarm's event type.

Deprecated in favour of the eventType block.

eventTypeobject

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

statestringunacknowledgedacknowledgedprocessed

The state of the alarm. Alarms start at "unacknowledged".

activeboolean

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

divisionobject

The division entity representing the division of the alarm. GET the href for full details.

Show child attributes
hrefstring<uri-reference>
eventobject

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

Show child attributes
hrefstring<uri-reference>

Link to the event page for this alarm. GET it for the fields found on an event that are not on an alarm.

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.

viewobject

POST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.

Show child attributes
hrefstring<uri-reference>
commentobject

POST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.

Show child attributes
hrefstring<uri-reference>
acknowledgeobject

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

Show child attributes
hrefstring<uri-reference>
acknowledgeWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
processobject

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

Show child attributes
hrefstring<uri-reference>
processWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
forceProcessobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

detailsstring

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

Show child attributes
timestring

The time the history entry was added.

actionstringlegacycommentacknowledgeprocessacknowledgeActiveescalatedviewed

The type of the history entry.

commentstring

The added comment, or a textual description of some occurrence related to the alarm.

operatorobject

The operator that created the history event.

instructionobject

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

Show child attributes
hrefstring<uri-reference>
namestring
cardholderobject

The cardholder entity associated with this alarm. GET the href for full details. Missing if this alarm does not have an associated cardholder.

Show child attributes
hrefstring<uri-reference>
namestring

In versions up to and including 8.10 this is the current name of the cardholder. In 8.20 it is the name of the cardholder at the time of the event.

firstNamestring

The current value of the firstName field of this cardholder. Added in 8.20.

lastNamestring

The current value of the lastName field of this cardholder. Added in 8.20.

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

idstring

An alphanumeric identifier for this alarm, unique to the server.

timestring

The time the alarm occurred.

messagestring

The alarm's message.

sourceobject

The ID and href are new to 8.10.

Show child attributes
idstring

The alphanumeric ID of the alarm's source item.

namestring

The name of the alarm's source item at the time of the event.

hrefstring<uri-reference>

Link to the source item.

typestring

The name of this alarm's event type.

Deprecated in favour of the eventType block.

eventTypeobject

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

statestringunacknowledgedacknowledgedprocessed

The state of the alarm. Alarms start at "unacknowledged".

activeboolean

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

divisionobject

The division entity representing the division of the alarm. GET the href for full details.

Show child attributes
hrefstring<uri-reference>
eventobject

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

Show child attributes
hrefstring<uri-reference>

Link to the event page for this alarm. GET it for the fields found on an event that are not on an alarm.

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.

viewobject

POST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.

Show child attributes
hrefstring<uri-reference>
commentobject

POST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.

Show child attributes
hrefstring<uri-reference>
acknowledgeobject

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

Show child attributes
hrefstring<uri-reference>
acknowledgeWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
processobject

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

Show child attributes
hrefstring<uri-reference>
processWithCommentobject

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

Show child attributes
hrefstring<uri-reference>
forceProcessobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object
timestring

The time the history entry was added.

actionstringlegacycommentacknowledgeprocessacknowledgeActiveescalatedviewed

The type of the history entry.

commentstring

The added comment, or a textual description of some occurrence related to the alarm.

operatorobject

The operator that created the history event.

Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this Alarm Zone.

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

shortNamestring
notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

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

commandsobject

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

Show child attributes
armobject
Show child attributes
hrefstring<uri-reference>

POST to this to arm this alarm zone until the next scheduled change.

namestringArmedSetArmed

Because a site can configure different names for different alarm zone states, they appear here.

armUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to arm this alarm zone for a fixed time.

namestringArmedSetArmed

Because a site can configure different names for different alarm zone states, they appear here.

disarmobject
Show child attributes
hrefstring<uri-reference>

POST to this to disarm this alarm zone until the next scheduled change.

namestringDisarmedUnsetDisarmed

Because a site can configure different names for different alarm zone states, they appear here.

disarmUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to disarm this alarm zone for a fixed time.

namestringDisarmedUnsetDisarmed

Because a site can configure different names for different alarm zone states, they appear here.

user1object
Show child attributes
hrefstring<uri-reference>

POST to this to set the alarm zone's state to user1 until the next scheduled change.

namestringUser1

Because a site can configure different names for different alarm zone states, they appear here.

user1Untilobject
Show child attributes
hrefstring<uri-reference>

POST to this to set the alarm zone's state to user1 for a fixed time.

namestringUser1

Because a site can configure different names for different alarm zone states, they appear here.

user2object
Show child attributes
hrefstring<uri-reference>

POST to this to set the alarm zone's state to user2 until the next scheduled change.

namestringUser2

Because a site can configure different names for different alarm zone states, they appear here.

user2Untilobject
Show child attributes
hrefstring<uri-reference>

POST to this to set the alarm zone's state to user2 for a fixed time.

namestringUser2

Because a site can configure different names for different alarm zone states, they appear here.

cancelobject
Show child attributes
hrefstring<uri-reference>

POST to this cancel an active override on the alarm zone, returning it to its scheduled mode.

This command will not be available 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).

Example
{
  "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"
    }
  }
}

HexString

string
HexString
Example
"0x0123456789ABCDEF"

DESFireEncodingRequest

object
  • 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.
csnHexStringrequired
cadFilesobject

Optional data read from CAD files, if present on the card.

reasonstring
Example
{
  "csn": "0x0123456789ABCDEF",
  "cadFiles": {},
  "reason": "string"
}

DESFireFile

object
idinteger

Index of the file within the application.

dataHexStringrequired
accessRightsHexStringrequired
communicationModeHexString
Example
{
  "id": 0,
  "data": "0x0123456789ABCDEF",
  "accessRights": "0x0123456789ABCDEF",
  "communicationMode": "0x0123456789ABCDEF"
}

DESFireKey

object
idinteger

Index of the key.

namestring

A description of the key. Not used in encoding.

dataHexStringrequired
versionHexString
Example
{
  "id": 0,
  "name": "string",
  "data": "0x0123456789ABCDEF",
  "version": "0x0123456789ABCDEF"
}

DESFireApplication

object
idHexString

Application ID in hex with leading 0x.

namestring

A description of the application. Not used in encoding.

keysArray<DESFireKey>

Map of key number or name to key details.

Show child attributes
idinteger

Index of the key.

namestring

A description of the key. Not used in encoding.

dataHexStringrequired
versionHexString
keySettings1HexString
keySettings2HexString
filesArray<DESFireFile>required

Map of file number to file content/access rights.

Show child attributes
idinteger

Index of the file within the application.

dataHexStringrequired
accessRightsHexStringrequired
communicationModeHexString
Example
{
  "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"
    }
  ]
}

EncryptedEncodingData

object
cipherTextstringrequired

The encrypted encoding data, encoded as a base64 string.

ivstringrequired

The initialization vector used for encryption, encoded as a base64 string.

ephemeralPublicKeystringrequired

The ephemeral public key used for encryption, encoded as a base64 string.

tagstringrequired

The authentication tag for the encrypted data, encoded as a base64 string.

Example
{
  "cipherText": "string",
  "iv": "string",
  "ephemeralPublicKey": "string",
  "tag": "string"
}

EncryptedEncodingDataResponseOrDecryptedForInfo

object
One of
encryptedEncodingDataEncryptedEncodingDatarequired
Show child attributes
cipherTextstringrequired

The encrypted encoding data, encoded as a base64 string.

ivstringrequired

The initialization vector used for encryption, encoded as a base64 string.

ephemeralPublicKeystringrequired

The ephemeral public key used for encryption, encoded as a base64 string.

tagstringrequired

The authentication tag for the encrypted data, encoded as a base64 string.

callbackCallbackLinksrequired
Show child attributes
encodeCompletedCallbackLinkrequired
Show child attributes
hrefstringrequired

Absolute or relative URL for callback invocation.

encodeFailedCallbackLinkrequired
Show child attributes
hrefstringrequired

Absolute or relative URL for callback invocation.

cardMasterKeyHexString
defaultAppKeyHexString
applicationsArray<object>
Show child attributes
idstring
namestring
keysArray<object>
Show child attributes
idinteger>= 0
namestring
dataHexString
versioninteger>= 1
keySettings1string
keySettings2string
filesArray<object>
Show child attributes
idinteger>= 0
dataHexString
accessRightsHexString
communicationModeHexString
sectorsArray<object>
Show child attributes
idinteger>= 0
namestring
blocksArray<object>
Show child attributes
idinteger>= 0
fileHexString
Example
{
  "encryptedEncodingData": {
    "cipherText": "string",
    "iv": "string",
    "ephemeralPublicKey": "string",
    "tag": "string"
  },
  "callback": {
    "encodeCompleted": {
      "href": "string"
    },
    "encodeFailed": {
      "href": "string"
    }
  }
}

EncryptedEncodingDataResponse

object
encryptedEncodingDataEncryptedEncodingDatarequired
Show child attributes
cipherTextstringrequired

The encrypted encoding data, encoded as a base64 string.

ivstringrequired

The initialization vector used for encryption, encoded as a base64 string.

ephemeralPublicKeystringrequired

The ephemeral public key used for encryption, encoded as a base64 string.

tagstringrequired

The authentication tag for the encrypted data, encoded as a base64 string.

callbackCallbackLinksrequired
Show child attributes
encodeCompletedCallbackLinkrequired
Show child attributes
hrefstringrequired

Absolute or relative URL for callback invocation.

encodeFailedCallbackLinkrequired
Show child attributes
hrefstringrequired

Absolute or relative URL for callback invocation.

Example
{
  "encryptedEncodingData": {
    "cipherText": "string",
    "iv": "string",
    "ephemeralPublicKey": "string",
    "tag": "string"
  },
  "callback": {
    "encodeCompleted": {
      "href": "string"
    },
    "encodeFailed": {
      "href": "string"
    }
  }
}

DecryptedDESFireDataForInfo

object

This is not actual API data. It is for information only.

cardMasterKeyHexString
defaultAppKeyHexString
applicationsArray<object>
Show child attributes
idstring
namestring
keysArray<object>
Show child attributes
idinteger>= 0
namestring
dataHexString
versioninteger>= 1
keySettings1string
keySettings2string
filesArray<object>
Show child attributes
idinteger>= 0
dataHexString
accessRightsHexString
communicationModeHexString
Example
{
  "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

object

This is not actual API data. It is for information only.

sectorsArray<object>
Show child attributes
idinteger>= 0
namestring
blocksArray<object>
Show child attributes
idinteger>= 0
fileHexString
Example
{
  "sectors": [
    {
      "id": 0,
      "name": "string",
      "blocks": [
        {
          "id": 0,
          "file": "0x0123456789ABCDEF"
        }
      ]
    }
  ]
}

DESFireEncodingResponse

object
applicationsArray<DESFireApplication>required

Map of application ID in hex with leading 0x to application details.

Show child attributes
idHexString

Application ID in hex with leading 0x.

namestring

A description of the application. Not used in encoding.

keysArray<DESFireKey>

Map of key number or name to key details.

Show child attributes
idinteger

Index of the key.

namestring

A description of the key. Not used in encoding.

dataHexStringrequired
versionHexString
keySettings1HexString
keySettings2HexString
filesArray<DESFireFile>required

Map of file number to file content/access rights.

Show child attributes
idinteger

Index of the file within the application.

dataHexStringrequired
accessRightsHexStringrequired
communicationModeHexString
cardMasterKeystring

The value of the AES key that the cards's master key would be set to if encoded from Command Centre

defaultAppKeystring

The value that the card's default app key would be set to if encoded from Command Centre

Example
{
  "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

object

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

ClassicBlocks
Example
{}

ClassicCad

object
sectorintegerrequired
blocksClassicBlocksrequired

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

Example
{
  "sector": 0,
  "blocks": {}
}

ClassicMad

object
blocksClassicBlocksrequired

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

Example
{
  "blocks": {}
}

ClassicEncodingRequest

object
  • 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.
csnHexStringrequired
cadClassicCad
Show child attributes
sectorintegerrequired
blocksClassicBlocksrequired

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

madClassicMad
Show child attributes
blocksClassicBlocksrequired

Map of block index to data block bytes. The block is binary data in hex string format that corresponds to a file in the sector.

unavailableSectorsArray<integer>
reasonstring
Example
{
  "csn": "0x0123456789ABCDEF",
  "cad": {
    "sector": 0,
    "blocks": {}
  },
  "mad": {
    "blocks": {}
  },
  "unavailableSectors": [
    0
  ],
  "reason": "string"
}

ClassicSector

object
namestring
idinteger
blocksArray<ClassicBlocks>required

List of blocks in this sector to be encoded.

Example
{
  "name": "string",
  "id": 0,
  "blocks": [
    {}
  ]
}

ClassicEncodingResponse

object
sectorsArray<ClassicSector>required

Map of sector number to sector data/blocks.

Show child attributes
namestring
idinteger
blocksArray<ClassicBlocks>required

List of blocks in this sector to be encoded.

Example
{
  "sectors": [
    {
      "name": "string",
      "id": 0,
      "blocks": [
        {}
      ]
    }
  ]
}

ErrorMessage

object
messagestring
Example
{
  "message": "string"
}

CardholderSearch

object

An array of cardholder summaries, and a next link for more.

resultsArray<CardholderSummary>

An array of cardholder summaries.

Show child attributes
hrefstring<uri-reference>read only

A 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 only

An alphanumeric identifier, unique to the server. No API calls use cardholder IDs.

Deprecated. Use the href instead.

firstNamestring
lastNamestring
shortNamestring

If you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it.

descriptionstring
authorisedboolean

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

nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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 only

A 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 only

An alphanumeric identifier, unique to the server. No API calls use cardholder IDs.

Deprecated. Use the href instead.

firstNamestring
lastNamestring
shortNamestring

If you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it.

descriptionstring
authorisedboolean

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

Example
{
  "href": "https://host.com:8904/api/cardholders/325",
  "id": "325",
  "firstName": "Algernon",
  "lastName": "Boothroyd",
  "shortName": "Q",
  "description": "Quartermaster",
  "authorised": true
}

CardholderCommonFields

object

The fields shared by the cardholder GET, POST, and PATCH.

disableCipherPadbooleandeprecated

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

divisionobject

The division containing this cardholder. You must send this when creating a cardholder.

Show child attributes
hrefstring<uri-reference>
notesstring

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

notificationsobject

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

Show child attributes
enabledboolean
fromstring<date-time>
untilstring<date-time>
oidcLoginEnabledboolean

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

oidcUserIdstring

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

operatorLoginEnabledboolean

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

operatorUsernamestring

The name that the operator uses when logging in to the interactive clients. Absent if blank.

Send the empty string "" to clear it.

operatorPasswordExpiredboolean

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

windowsLoginEnabledboolean

If true, and they have a Windows username, this user can log in using Windows integrated authentication.

windowsUsernamestring

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

Example
{
  "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 only

A 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 only

An alphanumeric identifier, unique to the server. No API calls use cardholder IDs.

Deprecated. Use the href instead.

firstNamestring
lastNamestring
shortNamestring

If you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it.

descriptionstring
authorisedboolean

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

disableCipherPadbooleandeprecated

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

divisionobject

The division containing this cardholder. You must send this when creating a cardholder.

Show child attributes
hrefstring<uri-reference>
notesstring

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

notificationsobject

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

Show child attributes
enabledboolean
fromstring<date-time>
untilstring<date-time>
oidcLoginEnabledboolean

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

oidcUserIdstring

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

operatorLoginEnabledboolean

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

operatorUsernamestring

The name that the operator uses when logging in to the interactive clients. Absent if blank.

Send the empty string "" to clear it.

operatorPasswordExpiredboolean

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

windowsLoginEnabledboolean

If true, and they have a Windows username, this user can log in using Windows integrated authentication.

windowsUsernamestring

This 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 only

The date and time of the last successful card event or operator-initiated movement (using a tag board or this API).

lastSuccessfulAccessZoneobjectread only

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

Show child attributes
hrefstring<uri-reference>
namestring
serverDisplayNamestringread only

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

usercodestring

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

operatorPasswordstring

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

Show child attributes
hrefstring<uri-reference>read only

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

numberstring

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

cardSerialNumberstring

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

statusobject

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

Show child attributes
valuestring

This card's state, taken from the card type's state set. The default card state set contains 'Active', 'Disabled (manually)', 'Lost', 'Stolen', and 'Damaged', or translations of those into the server's language. Your card state sets may differ, as they are customisable. (PIV cards)[#tag-PIV] have extra values.

In addition to the values in the card type's state set, it will be 'Not Yet Activated' if the card's activation date is in the future, 'Expired' if its deactivation date has passed, or 'Disabled (by inactivity)' if that is the site's policy and the card has not been used at a door.

Because the values of the value field is set by site administrators, you should not use it for programmatically determining whether a card is active. Use type instead. Use value for display.

When creating or updating a card, set value to one of the valid card states from the card state set. Command Centre is not fussy about case. If you omit it when you create a card, Command Centre will use the default for the card state set (which is 'active' for the factory state set).

If you omit both this and the PIV status when creating or updating a PIV card, or omit this and set the PIV status to 'normal' or 'offline', the server will set this to 'active'. Any other PIV status will cause the card to become inactive, regardless of what you put here. So you really only need to set this when you are changing the PIV status to 'normal' or 'offline' but you want to disable the card. In that case, set status.value to 'disabled (manually)'.

typestringpendingactiveexpiredinactiveread only

This will be 'pending' if the activation date (from) is in the future, 'expired' if its deactivation date (until) is in the past, or 'inactive' if it is disabled for one of the reasons given in the card state.

Never send type: Command Centre always infers it.

typeobject

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

Show child attributes
hrefstring<uri-reference>
namestring
invitationobject

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

Show child attributes
emailstring

The email address to which Command Centre will send or did send an invitation for this credential.

mobilestring

The telephone number to which Command Centre will send or did send an SMS containing a confirmation code for this invitation.

singleFactorOnlybooleanfalse

If you set this true Command Centre will not require a PIN or fingerprint from the cardholder when they enrol. Be aware that without that second authentication factor, zones in modes that require a PIN and readers that always require a second factor will not grant access to the cardholder.

singleFactorOnly will only be in the result of a GET if it is true. If it is false, the field will be missing.

statusstringnotSentsentexpiredacceptedread only
  • sent means Command Centre is waiting for the user to accept the invitation, either in Gallagher Mobile Connect or another app that uses the Mobile Connect SDK.

  • accepted means that the credential is ready for use.

  • notSent means the credential is only a few seconds old or Command Centre is having trouble contacting the cloud.

  • expired means that Command Centre did not receive a response in time.

hrefstring<uri-reference>read only

Mobile applications use this URL to accept this invitation. See the Mobile Connect SDK documentation for how to do that in your own applications.

Only present if status is 'sent'.

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 only

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

traceboolean

If 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 only

The 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 only

The 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 only

When a Gallagher controller last observed the credential in use.

pinstring

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

visitorContractorboolean

This 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 only

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

credentialIdstring

Reserved for use by Gallagher applications.

bleFacilityIdstring

Reserved for use by Gallagher applications.

encodingDataobjectread only

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

Show child attributes
mifareClassicobject
Show child attributes
hrefstring<uri-reference>
mifareDesfireobject
Show child attributes
hrefstring<uri-reference>
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'.

Show child attributes
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.

accessGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

The two fields in this block are read-only because they are determined by the from and until dates.

Show child attributes
valuestringread only

The state of this cardholder's access group membership, in the site's language. In an English locale the value is the same as the type, but capitalised.

typestringpendingexpiredactiveread only

The state of this cardholder's access group membership.

This will be 'pending' if the activation date is set and in the future, 'expired' if its deactivation date is set and in the past, or 'active'.

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.

operatorGroupsArray<CardholderOperatorGroup>

All the cardholder's operator group memberships. Added in 8.50.

Show child attributes
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.

operatorGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

competenciesArray<CardholderCompetency>

Every competency a cardholder possesses. There is quite a lot there: see the schema definition for detailed explanation.

Show child attributes
hrefstring<uri-reference>read only

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

competencyobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

This 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 flagEnablement dateExpiry datestatus.type
true-Far futureactive
true-Near futureexpiryDue
true-Pastexpired
falseFuture-pending
falsePast-inactive
falseUnset-inactive
Show child attributes
valuestring
typestring
disabledbooleanread onlydeprecated

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

enabledboolean

This 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 only

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

commentstring

The comment appears in the management clients when viewing the cardholder.

limitedCreditboolean

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

creditinteger

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

editobjectread only

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

Show child attributes
hrefstring<uri-reference>
updateLocationobjectread only

Link to POST to when you want to update the location of this cardholder. Added in 8.20.

Show child attributes
hrefstring<uri-reference>
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.

Show child attributes
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.

roleobject

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

cardholderobject

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

lockersArray<CardholderLocker>

All the cardholder's locker assignments.

Show child attributes
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.

lockerobject

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

elevatorGroupsArray<CardholderElevatorGroup>

The cardholder's elevator group properties. They may have one per elevator group.

Added in 8.50.

Show child attributes
hrefstring<uri-reference>

The href of this cardholder's elevator group entry. DELETE this to remove it from the cardholder.

elevatorGroupobject

The href and name of the elevator group for which this cardholder has a default floor or passenger type.

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

accessZoneobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

enableCaptureFeaturesboolean

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

enableCodeBlueFeaturesboolean

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

enableExpressFeaturesboolean

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

enableServiceFeaturesboolean

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

enableService2Featuresboolean

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

enableService3Featuresboolean

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

enableVipFeaturesboolean

VIP operation allows cardholders to swipe a card or enter a PIN to isolate the elevator and provide uninterrupted access to their designated floor.

updatesobject

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

Show child attributes
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.

Show child attributes
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.

typestringnormalEventscardholder

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

statusstringpendinginProgresscancelleddonefailed

The status of this redaction.

redactionOperatorobject

A block containing the href and current name of the operator who scheduled the redaction.

accessibilitiesArray<object>

The cardholder's accessibility settings in an array of three elements.

New to 9.50.

Show child attributes
typestringdisableCipherPaduseExtendedAccessTimepinExemption
  • disableCipherPad means this cardholder will 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.

  • useExtendedAccessTime increases the time a door remains unlocked and the amount of time that can pass before raising a 'door open too long' alarm.

  • pinExemption bypasses PIN entry. Users with this accessibility enabled who badge a card at a reader that normally requires PIN entry will not be prompted to enter a PIN.

enabledboolean

If false, the accessibility is disabled. If true, it depends on from and until.

fromstring<date-time>

If set and in the future, the accessibility will not be active until then. If not set or in the past, the accessibility will depend on enabled.

untilstring<date-time>

If set and in the past, the accessibility will have been inactive since then. If not set or in the future, the accessibility will depend on enabled.

If you send it, it must be later than both the from date, if sent, and the current time.

Example
{
  "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

object

A 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 '@'.

CardholderPDF
Example
{
  "@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

object
hrefstring<uri-reference>read only

GET 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 value field 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 only

The definition object is read-only: do not send it in a PATCH or POST.

Show child attributes
hrefstring<uri-reference>

This is the href of the PDF's definition, common to all cardholders who hold a value for this PDF.

namestring

The PDF's name, without an '@' prefix.

idstring

Short alphanumeric identifier used elsewhere in the API to filter cardholder searches and add PDF values to alarm and event GETs.

typestringstringimagestrEnumnumericdateaddressphoneemailmobile
valuestring | object

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

notificationsboolean

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

Example
{
  "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

object

A 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 only

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

numberstring

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

cardSerialNumberstring

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

statusobject

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

Show child attributes
valuestring

This card's state, taken from the card type's state set. The default card state set contains 'Active', 'Disabled (manually)', 'Lost', 'Stolen', and 'Damaged', or translations of those into the server's language. Your card state sets may differ, as they are customisable. (PIV cards)[#tag-PIV] have extra values.

In addition to the values in the card type's state set, it will be 'Not Yet Activated' if the card's activation date is in the future, 'Expired' if its deactivation date has passed, or 'Disabled (by inactivity)' if that is the site's policy and the card has not been used at a door.

Because the values of the value field is set by site administrators, you should not use it for programmatically determining whether a card is active. Use type instead. Use value for display.

When creating or updating a card, set value to one of the valid card states from the card state set. Command Centre is not fussy about case. If you omit it when you create a card, Command Centre will use the default for the card state set (which is 'active' for the factory state set).

If you omit both this and the PIV status when creating or updating a PIV card, or omit this and set the PIV status to 'normal' or 'offline', the server will set this to 'active'. Any other PIV status will cause the card to become inactive, regardless of what you put here. So you really only need to set this when you are changing the PIV status to 'normal' or 'offline' but you want to disable the card. In that case, set status.value to 'disabled (manually)'.

typestringpendingactiveexpiredinactiveread only

This will be 'pending' if the activation date (from) is in the future, 'expired' if its deactivation date (until) is in the past, or 'inactive' if it is disabled for one of the reasons given in the card state.

Never send type: Command Centre always infers it.

typeobject

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

Show child attributes
hrefstring<uri-reference>
namestring
invitationobject

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

Show child attributes
emailstring

The email address to which Command Centre will send or did send an invitation for this credential.

mobilestring

The telephone number to which Command Centre will send or did send an SMS containing a confirmation code for this invitation.

singleFactorOnlybooleanfalse

If you set this true Command Centre will not require a PIN or fingerprint from the cardholder when they enrol. Be aware that without that second authentication factor, zones in modes that require a PIN and readers that always require a second factor will not grant access to the cardholder.

singleFactorOnly will only be in the result of a GET if it is true. If it is false, the field will be missing.

statusstringnotSentsentexpiredacceptedread only
  • sent means Command Centre is waiting for the user to accept the invitation, either in Gallagher Mobile Connect or another app that uses the Mobile Connect SDK.

  • accepted means that the credential is ready for use.

  • notSent means the credential is only a few seconds old or Command Centre is having trouble contacting the cloud.

  • expired means that Command Centre did not receive a response in time.

hrefstring<uri-reference>read only

Mobile applications use this URL to accept this invitation. See the Mobile Connect SDK documentation for how to do that in your own applications.

Only present if status is 'sent'.

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 only

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

traceboolean

If 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 only

The 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 only

The 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 only

When a Gallagher controller last observed the credential in use.

pinstring

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

visitorContractorboolean

This 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 only

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

credentialIdstring

Reserved for use by Gallagher applications.

bleFacilityIdstring

Reserved for use by Gallagher applications.

encodingDataobjectread only

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

Show child attributes
mifareClassicobject
Show child attributes
hrefstring<uri-reference>
mifareDesfireobject
Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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

accessGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

The two fields in this block are read-only because they are determined by the from and until dates.

Show child attributes
valuestringread only

The state of this cardholder's access group membership, in the site's language. In an English locale the value is the same as the type, but capitalised.

typestringpendingexpiredactiveread only

The state of this cardholder's access group membership.

This will be 'pending' if the activation date is set and in the future, 'expired' if its deactivation date is set and in the past, or 'active'.

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.

Example
{
  "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

object

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

operatorGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

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

object

A 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 only

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

competencyobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

This 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 flagEnablement dateExpiry datestatus.type
true-Far futureactive
true-Near futureexpiryDue
true-Pastexpired
falseFuture-pending
falsePast-inactive
falseUnset-inactive
Show child attributes
valuestring
typestring
disabledbooleanread onlydeprecated

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

enabledboolean

This 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 only

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

commentstring

The comment appears in the management clients when viewing the cardholder.

limitedCreditboolean

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

creditinteger

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

Example
{
  "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

object

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

roleobject

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

cardholderobject

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

Example
{
  "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

object

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

lockerobject

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

Example
{
  "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

object

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

elevatorGroupobject

The href and name of the elevator group for which this cardholder has a default floor or passenger type.

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

accessZoneobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

enableCaptureFeaturesboolean

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

enableCodeBlueFeaturesboolean

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

enableExpressFeaturesboolean

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

enableServiceFeaturesboolean

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

enableService2Featuresboolean

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

enableService3Featuresboolean

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

enableVipFeaturesboolean

VIP operation allows cardholders to swipe a card or enter a PIN to isolate the elevator and provide uninterrupted access to their designated floor.

Example
{
  "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

object

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.

firstNamestring

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

lastNamestring

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

shortNamestring
descriptionstring
authorisedboolean

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

divisionobjectrequired

Mandatory when creating any cardholder. In this example, we want all students in division 5387.

@emailstring

An example PDF value. In this example, access group 352 must include a PDF called 'email' otherwise this will appear to have no effect.

@headshotstring

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

Show child attributes
hrefstring<uri-reference>read only

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

numberstring

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

cardSerialNumberstring

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

statusobject

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

Show child attributes
valuestring

This card's state, taken from the card type's state set. The default card state set contains 'Active', 'Disabled (manually)', 'Lost', 'Stolen', and 'Damaged', or translations of those into the server's language. Your card state sets may differ, as they are customisable. (PIV cards)[#tag-PIV] have extra values.

In addition to the values in the card type's state set, it will be 'Not Yet Activated' if the card's activation date is in the future, 'Expired' if its deactivation date has passed, or 'Disabled (by inactivity)' if that is the site's policy and the card has not been used at a door.

Because the values of the value field is set by site administrators, you should not use it for programmatically determining whether a card is active. Use type instead. Use value for display.

When creating or updating a card, set value to one of the valid card states from the card state set. Command Centre is not fussy about case. If you omit it when you create a card, Command Centre will use the default for the card state set (which is 'active' for the factory state set).

If you omit both this and the PIV status when creating or updating a PIV card, or omit this and set the PIV status to 'normal' or 'offline', the server will set this to 'active'. Any other PIV status will cause the card to become inactive, regardless of what you put here. So you really only need to set this when you are changing the PIV status to 'normal' or 'offline' but you want to disable the card. In that case, set status.value to 'disabled (manually)'.

typestringpendingactiveexpiredinactiveread only

This will be 'pending' if the activation date (from) is in the future, 'expired' if its deactivation date (until) is in the past, or 'inactive' if it is disabled for one of the reasons given in the card state.

Never send type: Command Centre always infers it.

typeobject

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

Show child attributes
hrefstring<uri-reference>
namestring
invitationobject

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

Show child attributes
emailstring

The email address to which Command Centre will send or did send an invitation for this credential.

mobilestring

The telephone number to which Command Centre will send or did send an SMS containing a confirmation code for this invitation.

singleFactorOnlybooleanfalse

If you set this true Command Centre will not require a PIN or fingerprint from the cardholder when they enrol. Be aware that without that second authentication factor, zones in modes that require a PIN and readers that always require a second factor will not grant access to the cardholder.

singleFactorOnly will only be in the result of a GET if it is true. If it is false, the field will be missing.

statusstringnotSentsentexpiredacceptedread only
  • sent means Command Centre is waiting for the user to accept the invitation, either in Gallagher Mobile Connect or another app that uses the Mobile Connect SDK.

  • accepted means that the credential is ready for use.

  • notSent means the credential is only a few seconds old or Command Centre is having trouble contacting the cloud.

  • expired means that Command Centre did not receive a response in time.

hrefstring<uri-reference>read only

Mobile applications use this URL to accept this invitation. See the Mobile Connect SDK documentation for how to do that in your own applications.

Only present if status is 'sent'.

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 only

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

traceboolean

If 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 only

The 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 only

The 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 only

When a Gallagher controller last observed the credential in use.

pinstring

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

visitorContractorboolean

This 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 only

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

credentialIdstring

Reserved for use by Gallagher applications.

bleFacilityIdstring

Reserved for use by Gallagher applications.

encodingDataobjectread only

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

Show child attributes
mifareClassicobject
Show child attributes
hrefstring<uri-reference>
mifareDesfireobject
Show child attributes
hrefstring<uri-reference>
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.

Show child attributes
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.

accessGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

The two fields in this block are read-only because they are determined by the from and until dates.

Show child attributes
valuestringread only

The state of this cardholder's access group membership, in the site's language. In an English locale the value is the same as the type, but capitalised.

typestringpendingexpiredactiveread only

The state of this cardholder's access group membership.

This will be 'pending' if the activation date is set and in the future, 'expired' if its deactivation date is set and in the past, or 'active'.

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.

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.

Show child attributes
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.

operatorGroupobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

competenciesArray<CardholderCompetency>

In this example we are giving our new cardholder a disabled competency, set to enable in January 2019.

Show child attributes
hrefstring<uri-reference>read only

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

competencyobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

statusobjectread only

This 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 flagEnablement dateExpiry datestatus.type
true-Far futureactive
true-Near futureexpiryDue
true-Pastexpired
falseFuture-pending
falsePast-inactive
falseUnset-inactive
Show child attributes
valuestring
typestring
disabledbooleanread onlydeprecated

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

enabledboolean

This 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 only

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

commentstring

The comment appears in the management clients when viewing the cardholder.

limitedCreditboolean

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

creditinteger

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

notesstring
notificationsobject

You can set or update any of the three fields in this block.

Show child attributes
enabledboolean
fromstring<date-time>
untilstring<date-time>
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.

Show child attributes
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.

roleobject

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

cardholderobject

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

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.

Show child attributes
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.

lockerobject

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

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.

Show child attributes
hrefstring<uri-reference>

The href of this cardholder's elevator group entry. DELETE this to remove it from the cardholder.

elevatorGroupobject

The href and name of the elevator group for which this cardholder has a default floor or passenger type.

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

accessZoneobject

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

Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

enableCaptureFeaturesboolean

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

enableCodeBlueFeaturesboolean

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

enableExpressFeaturesboolean

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

enableServiceFeaturesboolean

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

enableService2Featuresboolean

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

enableService3Featuresboolean

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

enableVipFeaturesboolean

VIP operation allows cardholders to swipe a card or enter a PIN to isolate the elevator and provide uninterrupted access to their designated floor.

Example
{
  "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

object

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.

authorisedboolean
firstNamestring
@employeeIdstring

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

cardsobject

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

accessGroupsobject

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

competenciesobject

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

relationshipsobject

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

lockersobject

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

operatorGroupsobject

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

elevatorGroupsobject

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

Example
{
  "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

object

An array of cardholder changes, described in the next section, and a next link for more.

resultsArray<CardholderChange>

An array of cardholder changes.

Show child attributes
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.

typestringaddupdateremove

add if this change added a cardholder, update if it modified a cardholder, or remove if it deleted a cardholder.

itemobject

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

operatorobject

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

oldValuesany

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

newValuesany

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

cardholderany

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

nextobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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 oldValues and newValues blocks, because modifying a group membership changes its ID.

  • The API will not notify changes to a competency's credit integer or limitedCredit boolean. 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 lastSuccessfulAccessTime or lastSuccessfulAccessZone.

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.

typestringaddupdateremove

add if this change added a cardholder, update if it modified a cardholder, or remove if it deleted a cardholder.

itemobject

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

operatorobject

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

oldValuesany

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

newValuesany

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

cardholderany

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

Example
{
  "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

object

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

Show child attributes
hrefstring<uri-reference>read only

This is the identifier to use when assigning a card to a cardholder in a cardholder PATCH or POST.

This is also the URL for the card type's detail page. GETting it will return you a 404 if you do not have 'View site' or 'Configure site' on the card type's division.

idstringread only

The API does not use this field. Nor should you.

namestring
divisionobject

The division that contains this card type. Required when creating a card type.

notesstring

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

facilityCodestring

A 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 only

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

credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransact

Required when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.

minimumNumberstring

For card types with integer card numbers, this is the minimum. Must be non-negative.

maximumNumberstring

For card types with integer card numbers, this is the maximum. Must be non-negative.

serverDisplayNamestringread only

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

regexstring

This is the regular expression that a text card number must match before Command Centre will accept it.

regexDescriptionstring

Regular expressions often need explaining to your users.

cardStateSetany

Documentation coming. TODO

POST only.

defaultIssueLevelany

Documentation coming. TODO

cardIssueLevelMustMatchDefaultany

Documentation coming. TODO

enableExpiryNotificationsany

Documentation coming. TODO

warningPeriodany

Documentation coming. TODO

pinLengthany

Documentation coming. TODO

inactivityTimeoutInDaysany

Documentation coming. TODO

allowReprintany

Documentation coming. TODO

allowReEncodeany

Documentation coming. TODO

isEngageany

Documentation coming. TODO

sendRegistrationEmailany

Documentation coming. TODO

sendRegistrationSmsany

Documentation coming. TODO

warningPeriodTypeany

Documentation coming. TODO

Days, Weeks, Months, or Years.

cardNumberFormatany

Documentation coming. TODO

POST only.

defaultExpiryTypeany

Documentation coming. TODO

ExplicitDateTime or Duration.

defaultExpiryDurationany

Documentation coming. TODO

(if the type is duration)

defaultExpiryFromany

Documentation coming. TODO

defaultExpiryUntilany

Documentation coming. TODO

(if defaultExpiryType is explicitDateTime)

engageCardFormatany

Documentation coming. TODO

Only if isEngage is true. There are nine acceptable values.

appleTemplateIdany

Documentation coming. TODO

Required if credential class is Apple Pass.

appleLinkedCredentials |any

Documentation coming. TODO

The POST format is different from the PATCH format's add and remove arrays.

nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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 only

This is the identifier to use when assigning a card to a cardholder in a cardholder PATCH or POST.

This is also the URL for the card type's detail page. GETting it will return you a 404 if you do not have 'View site' or 'Configure site' on the card type's division.

idstringread only

The API does not use this field. Nor should you.

namestring
divisionobject

The division that contains this card type. Required when creating a card type.

notesstring

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

facilityCodestring

A 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 only

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

credentialClassstringpivpivicardmobiledigitalIdgovPasstrackingTagtransact

Required when creating a new card type but ignored when modifying one since a credential's type cannot be changed once set.

minimumNumberstring

For card types with integer card numbers, this is the minimum. Must be non-negative.

maximumNumberstring

For card types with integer card numbers, this is the maximum. Must be non-negative.

serverDisplayNamestringread only

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

regexstring

This is the regular expression that a text card number must match before Command Centre will accept it.

regexDescriptionstring

Regular expressions often need explaining to your users.

cardStateSetany

Documentation coming. TODO

POST only.

defaultIssueLevelany

Documentation coming. TODO

cardIssueLevelMustMatchDefaultany

Documentation coming. TODO

enableExpiryNotificationsany

Documentation coming. TODO

warningPeriodany

Documentation coming. TODO

pinLengthany

Documentation coming. TODO

inactivityTimeoutInDaysany

Documentation coming. TODO

allowReprintany

Documentation coming. TODO

allowReEncodeany

Documentation coming. TODO

isEngageany

Documentation coming. TODO

sendRegistrationEmailany

Documentation coming. TODO

sendRegistrationSmsany

Documentation coming. TODO

warningPeriodTypeany

Documentation coming. TODO

Days, Weeks, Months, or Years.

cardNumberFormatany

Documentation coming. TODO

POST only.

defaultExpiryTypeany

Documentation coming. TODO

ExplicitDateTime or Duration.

defaultExpiryDurationany

Documentation coming. TODO

(if the type is duration)

defaultExpiryFromany

Documentation coming. TODO

defaultExpiryUntilany

Documentation coming. TODO

(if defaultExpiryType is explicitDateTime)

engageCardFormatany

Documentation coming. TODO

Only if isEngage is true. There are nine acceptable values.

appleTemplateIdany

Documentation coming. TODO

Required if credential class is Apple Pass.

appleLinkedCredentials |any

Documentation coming. TODO

The POST format is different from the PATCH format's add and remove arrays.

Example
{
  "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

object

An array of competency summaries, and a next link for more.

resultsArray<CompetencySummary>

An array of competency summaries.

Show child attributes
hrefstring<uri-reference>

This is the identifier to use when assigning a competency to a cardholder in a PATCH or POST.

idstring
namestring
descriptionstring
serverDisplayNamestring
notesstring
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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.

hrefstring<uri-reference>

This is the identifier to use when assigning a competency to a cardholder in a PATCH or POST.

idstring
namestring
descriptionstring
serverDisplayNamestring
notesstring
Example
{
  "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.

hrefstring<uri-reference>

This is the identifier to use when assigning a competency to a cardholder in a PATCH or POST.

idstring
namestring
descriptionstring
serverDisplayNamestring
notesstring
divisionobject

The division containing this competency.

shortNamestring
expiryNotifyboolean

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

noticePeriodany

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

Show child attributes
unitsstringdaysweeksmonthsyears
numberinteger[0, 999]0
defaultExpiryobject

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

Show child attributes
expiryTypestringnonedurationdaysdurationweeksdurationmonthsdurationyearsdatenone

If 'none', there is no default expiry.

expiryValueany

This field will be missing if 'expiryType' is 'none', a string containing a date-time if 'expiryType' is 'date', or an unquoted integer between zero and 999 otherwise.

Zero is a valid value. Because durations are always rounded up to shortly before midnight, an expiryValue of zero means that the competency will be active for the rest of the day on which the operator grants it.

defaultAccessstringnoAccessreadOnlyfullAccessfullAccess

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

Example
{
  "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

object

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

Show child attributes
hrefstring<uri-reference>

A self reference.

idstring

The alphanumeric ID of the division. Use this ID in the division filter when requesting events.

namestring

The division's name.

descriptionstring

The division's description. New in 8.50. Not sent by default; ask for it with fields.

serverDisplayNamestringread only

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

parentobject

An object containing an href to the division entity representing the current division's parent.

All divisions except the root division have a parent.

Show child attributes
hrefstring<uri-reference>
visitorManagementobject

An 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').

Show child attributes
activeboolean

If present and true, this division has its own visitor management configuration. Otherwise it uses its parent's. The same thing is indicated by the presence or absence of the visitorTypes block.

visitorTypesArray<object>

This is an array of items, each containing a 'visitor type'. A visitor type comprises three things: an access group, to which visitor management will add every visitor as soon as they are on the visit, host access groups, to one of which the host cardholder must belong, and visitor access groups, to which Command Centre will add a visitor when they sign in.

When creating a visit you must pick a visitor type that is in the same division as the visit's reception. The visitor type you pick determines which host you can assign (he or she must be a member of at least one of the visitor type's host access groups) and which visitor access groups you can assign (they must be in the visitor type's list of visitor access groups).

Show child attributes
hrefstring<uri-reference>

This is the href you should use for a visitor type when you create or update a visit.

It is only used for identification: GETting it will 404.

accessGroupobject

The name and href of an access group.

Command Centre will add cardholders to this access group as soon as you add them to a visit of this type. The purpose of this group is to grant access to PDFs for personal data that will help them sign in on the day, such as passport and driver's licence numbers, and photos.

Show child attributes
namestring
hrefstring<uri-reference>
hostAccessGroupsArray<object>

Names and hrefs of more access groups. When you create or modify a visit of this type, its host cardholder must belong to one of these.

A host is the cardholder Command Centre notifies when a visitor arrives, and is ultimately responsible for the visitor while on site.

Show child attributes
accessGroupobject
Show child attributes
namestring
hrefstring<uri-reference>
visitorAccessGroupsArray<object>

An array of access groups.

Command Centre will add visitors to a visit's 'visitor access groups' when they sign in. Their purpose is to grant visitors access through the site's doors so that they can move around the site.

When creating a visit using this visitor type, you can only pick visitor access groups from this list.

The server puts each group's name and href inside a block called accessGroup, rather than in the root of the array element, to allow for expansion in a future version.

Show child attributes
accessGroupobject

The name and href of an access group. Command Centre will add the visitors on the visit to this access group when they sign in.

Show child attributes
namestring
hrefstring<uri-reference>
nextobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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

idstring

The alphanumeric ID of the division. Use this ID in the division filter when requesting events.

namestring

The division's name.

descriptionstring

The division's description. New in 8.50. Not sent by default; ask for it with fields.

serverDisplayNamestringread only

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

parentobject

An object containing an href to the division entity representing the current division's parent.

All divisions except the root division have a parent.

Show child attributes
hrefstring<uri-reference>
visitorManagementobject

An 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').

Show child attributes
activeboolean

If present and true, this division has its own visitor management configuration. Otherwise it uses its parent's. The same thing is indicated by the presence or absence of the visitorTypes block.

visitorTypesArray<object>

This is an array of items, each containing a 'visitor type'. A visitor type comprises three things: an access group, to which visitor management will add every visitor as soon as they are on the visit, host access groups, to one of which the host cardholder must belong, and visitor access groups, to which Command Centre will add a visitor when they sign in.

When creating a visit you must pick a visitor type that is in the same division as the visit's reception. The visitor type you pick determines which host you can assign (he or she must be a member of at least one of the visitor type's host access groups) and which visitor access groups you can assign (they must be in the visitor type's list of visitor access groups).

Show child attributes
hrefstring<uri-reference>

This is the href you should use for a visitor type when you create or update a visit.

It is only used for identification: GETting it will 404.

accessGroupobject

The name and href of an access group.

Command Centre will add cardholders to this access group as soon as you add them to a visit of this type. The purpose of this group is to grant access to PDFs for personal data that will help them sign in on the day, such as passport and driver's licence numbers, and photos.

Show child attributes
namestring
hrefstring<uri-reference>
hostAccessGroupsArray<object>

Names and hrefs of more access groups. When you create or modify a visit of this type, its host cardholder must belong to one of these.

A host is the cardholder Command Centre notifies when a visitor arrives, and is ultimately responsible for the visitor while on site.

Show child attributes
accessGroupobject
Show child attributes
namestring
hrefstring<uri-reference>
visitorAccessGroupsArray<object>

An array of access groups.

Command Centre will add visitors to a visit's 'visitor access groups' when they sign in. Their purpose is to grant visitors access through the site's doors so that they can move around the site.

When creating a visit using this visitor type, you can only pick visitor access groups from this list.

The server puts each group's name and href inside a block called accessGroup, rather than in the root of the array element, to allow for expansion in a future version.

Show child attributes
accessGroupobject

The name and href of an access group. Command Centre will add the visitors on the visit to this access group when they sign in.

Show child attributes
namestring
hrefstring<uri-reference>
Example
{
  "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

object

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.

namestring

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

descriptionstring

The division's description.

notesstring

A string, able to be much longer than description, suitable for holding notes about the division.

parentobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "name": "Long division",
  "description": "Quatermasters",
  "notes": "A very long string.",
  "parent": {
    "href": "https://localhost:8904/api/divisions/2"
  }
}

DoorSearch

object

An array of door summaries, and a next link for more.

resultsArray<DoorSummary>

An array of door summaries.

Show child attributes
hrefstring<uri-reference>

A link to a door detail object for this door.

idstring

An 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
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this door.

entryAccessZoneobject

The name and href of the access zone to which this door allows entry.

Show child attributes
namestring
hrefstring<uri-reference>

This is the address of the access zone's detail page (which includes a link back here in its doors array).

exitAccessZoneobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the address of the access zone's detail page (which includes a link back here in its doors array).

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

commandsobject

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

Show child attributes
openobject
Show child attributes
hrefstring<uri-reference>

POST to this to open this door.

lockobject
Show child attributes
hrefstring<uri-reference>

POST to this to lock this door.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because in 9.50 there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

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

Example
{
  "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

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.

namestring
Example
{
  "href": "https://localhost:8904/api/elevator_groups/635",
  "name": "Main building lower floors"
}

ElevatorGroupFloorAccessDetail

object

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

namestring
divisionobject

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

Show child attributes
floorNumberinteger>= 1

This is Command Centre's internal identifier for the floor. Passengers do not see it.

frontServiceboolean

True iff the elevator car has a front-facing door and the elevator services the floor with the front of the elevator.

This field requires the RESTStatus licence in versions up to and including 8.50. Starting with 8.60 it requires either the RESTStatus or the RESTOverrides licence.

rearServiceboolean

True iff the elevator car has a rear-facing door and the elevator services the floor with the rear of the elevator.

This field requires the RESTStatus licence in versions up to and including 8.50. Starting with 8.60 it requires either the RESTStatus or the RESTOverrides licence.

floorNamestring

This is a friendly name that Command Centre presents to operators as the name of the floor.

frontAccessZoneobject

The access zone into which the elevator car's front door opens. It will be missing if the car's front door does not open on this floor.

Show child attributes
idstring

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

namestring
hrefstring<uri-reference>
rearAccessZoneobject

The access zone into which the elevator car's rear door opens. It will be missing if the car's rear door does not open on this floor, or if the elevator group's rearAccessEnabled is false.

Show child attributes
idstring

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

namestring
hrefstring<uri-reference>
Example
{
  "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

object

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

namestring
descriptionstring
notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

elevatorGroupNumberinteger

The elevator system's internal identifier for this elevator group.

elevatorSystemobject
Show child attributes
idstring

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

rearAccessEnabledboolean

True only if the elevator group uses rear doors on its cars to service the other side of the shaft.

groundFloorNumberinteger>= 1

The identifier of the floor that this elevator group calls 'ground'.

Example
{
  "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.

Show child attributes
hrefstring<uri-reference>

A link to this event's details.

idstring

An alphanumeric identifier for this event, unique to the server.

serverDisplayNamestring

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

messagestring

The event's message.

occurrencesinteger>= 2

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

alarmobject

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

Show child attributes
statestringunacknowledgedacknowledgedprocessed

Alarms start unacknowledged. Acknowledging or processing them changes that.

hrefstring<uri-reference>

Link to the alarm entity corresponding to the event. GET the href for full alarm details.

operatorobject

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

Show child attributes
hrefstring<uri-reference>

Link to the operator who caused this event by editing an item. New in 8.00.

namestring

The name the operator held at the time. New in 8.40.

sourceobject

ID and name of the source of the event, as recorded at the time of the event.

Show child attributes
idstring

The alphanumeric ID of the event source item. Search for events with the same source as this one with source=321 in the query parameters.

namestring

This could be different from the current name of the source item.

hrefstring<uri-reference>

Link to the source item. New in 8.00.

groupobject

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

Show child attributes
idstring

The alphanumeric ID of the event group. Search for events of the same rough category as this one with type=35 in the query parameters.

namestring

The name of the event group.

typeobject

ID and name of the event's type. There is a long list of them at /events/groups.

Show child attributes
idstring

The alphanumeric ID of the event type. To search for events of the same type as this example, put type=23035 in the query parameters of /events.

namestring

The name of the event type.

eventTypeobject

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

divisionobject

ID, name, and href of the event's division (which is the division of the event's source item, for most event types).

Show child attributes
idstring

The alphanumeric ID of the event's division. Search for other events from items in the same division as this example by putting division=2 in the query parameters.

hrefstring<uri-reference>

The link to the division item.

namestring

The division's name. Added in 8.40.

cardholderobject

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

Show child attributes
hrefstring<uri-reference>

Link to the cardholder entity representing the cardholder of the event. GET the href for full details.

idstring

The alphanumeric ID of the cardholder associated with this event.

namestring

In versions up to and including 8.10 this is the current name of the cardholder. In 8.20 it is the name of the cardholder at the time of the event.

firstNamestring

The current value of the firstName field of this cardholder. Added in 8.20.

lastNamestring

The current value of the lastName field of this cardholder. Added in 8.20.

entryAccessZoneany

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

Show child attributes
hrefstring<uri-reference>

Link to the access zone entity representing the entry access zone related to this event. GET the href for the access zone's full details. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The current name of the access zone. Expect a future version of Command Centre to change this to change to the name of the access zone at the time of the event.

idstring

Deprecated.

exitAccessZoneany

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

Show child attributes
hrefstring<uri-reference>

Link to the access zone entity representing the exit access zone related to this event. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The current name of the exit access zone. Expect a future version of Command Centre to change this to change to the name of the exit access zone at the time of the event.

idstring

Deprecated.

doorany

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

Show child attributes
hrefstring<uri-reference>

Link to the entity representing the door related to this event. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The name the door had when the event occurred.

accessGroupobject

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

Show child attributes
hrefstring<uri-reference>

Link to the access group related to this event.

cardobject

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

Show child attributes
facilityCodestring

The card's facility code at the time of the event expressed as one letter followed by up to five digits.

numberstring

The card's number at the time of the event. Despite the name it may not necessarily be an actual number; mobile card numbers are arbitrary strings, for example.

Note that card numbers are not guaranteed unique. The combination of facility code, issue level, and card number will be unique for card types that have a facility code and issue level.

Before 8.60 this number was a signed 32-bit integer, so numeric card numbers greater than 2^31 came out negative and mobile card numbers were arbitrary. In 8.60 numeric card numbers are positive and mobile card numbers are their ID strings, which operators can set to anything, and default to GUIDs.

issueLevelinteger

The issue level of the card at the time.

modifiedItemobject

The href and type of the item that an operator created, changed, or deleted, for those kinds of events.

New in 8.40.

Show child attributes
hrefstring<uri-reference>

Link to the item that this event modified. Watch for 404s: this link will be here even for deleted items.

typeobject
Show child attributes
idstring

A short alphanum identifying the item's type.

namestring

A human-readable name of the item's type, suitable for display.

This string is translated using the installation's language pack, and Gallagher reserves the right to change item type names in new versions, so you should not do anything with this string except show it to a person.

nextobject

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

Show child attributes
hrefstring<uri-reference>
previousobject

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

Show child attributes
hrefstring<uri-reference>
updatesobject

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

Show child attributes
hrefstring<uri-reference>
previousobject

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

Show child attributes
hrefstring<uri-reference>
nextobject

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

Show child attributes
hrefstring<uri-reference>
updatesobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An alphanumeric identifier for this event, unique to the server.

serverDisplayNamestring

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

messagestring

The event's message.

occurrencesinteger>= 2

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

alarmobject

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

Show child attributes
statestringunacknowledgedacknowledgedprocessed

Alarms start unacknowledged. Acknowledging or processing them changes that.

hrefstring<uri-reference>

Link to the alarm entity corresponding to the event. GET the href for full alarm details.

operatorobject

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

Show child attributes
hrefstring<uri-reference>

Link to the operator who caused this event by editing an item. New in 8.00.

namestring

The name the operator held at the time. New in 8.40.

sourceobject

ID and name of the source of the event, as recorded at the time of the event.

Show child attributes
idstring

The alphanumeric ID of the event source item. Search for events with the same source as this one with source=321 in the query parameters.

namestring

This could be different from the current name of the source item.

hrefstring<uri-reference>

Link to the source item. New in 8.00.

groupobject

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

Show child attributes
idstring

The alphanumeric ID of the event group. Search for events of the same rough category as this one with type=35 in the query parameters.

namestring

The name of the event group.

typeobject

ID and name of the event's type. There is a long list of them at /events/groups.

Show child attributes
idstring

The alphanumeric ID of the event type. To search for events of the same type as this example, put type=23035 in the query parameters of /events.

namestring

The name of the event type.

eventTypeobject

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

divisionobject

ID, name, and href of the event's division (which is the division of the event's source item, for most event types).

Show child attributes
idstring

The alphanumeric ID of the event's division. Search for other events from items in the same division as this example by putting division=2 in the query parameters.

hrefstring<uri-reference>

The link to the division item.

namestring

The division's name. Added in 8.40.

cardholderobject

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

Show child attributes
hrefstring<uri-reference>

Link to the cardholder entity representing the cardholder of the event. GET the href for full details.

idstring

The alphanumeric ID of the cardholder associated with this event.

namestring

In versions up to and including 8.10 this is the current name of the cardholder. In 8.20 it is the name of the cardholder at the time of the event.

firstNamestring

The current value of the firstName field of this cardholder. Added in 8.20.

lastNamestring

The current value of the lastName field of this cardholder. Added in 8.20.

entryAccessZoneany

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

Show child attributes
hrefstring<uri-reference>

Link to the access zone entity representing the entry access zone related to this event. GET the href for the access zone's full details. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The current name of the access zone. Expect a future version of Command Centre to change this to change to the name of the access zone at the time of the event.

idstring

Deprecated.

exitAccessZoneany

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

Show child attributes
hrefstring<uri-reference>

Link to the access zone entity representing the exit access zone related to this event. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The current name of the exit access zone. Expect a future version of Command Centre to change this to change to the name of the exit access zone at the time of the event.

idstring

Deprecated.

doorany

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

Show child attributes
hrefstring<uri-reference>

Link to the entity representing the door related to this event. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The name the door had when the event occurred.

accessGroupobject

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

Show child attributes
hrefstring<uri-reference>

Link to the access group related to this event.

cardobject

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

Show child attributes
facilityCodestring

The card's facility code at the time of the event expressed as one letter followed by up to five digits.

numberstring

The card's number at the time of the event. Despite the name it may not necessarily be an actual number; mobile card numbers are arbitrary strings, for example.

Note that card numbers are not guaranteed unique. The combination of facility code, issue level, and card number will be unique for card types that have a facility code and issue level.

Before 8.60 this number was a signed 32-bit integer, so numeric card numbers greater than 2^31 came out negative and mobile card numbers were arbitrary. In 8.60 numeric card numbers are positive and mobile card numbers are their ID strings, which operators can set to anything, and default to GUIDs.

issueLevelinteger

The issue level of the card at the time.

modifiedItemobject

The href and type of the item that an operator created, changed, or deleted, for those kinds of events.

New in 8.40.

Show child attributes
hrefstring<uri-reference>

Link to the item that this event modified. Watch for 404s: this link will be here even for deleted items.

typeobject
Show child attributes
idstring

A short alphanum identifying the item's type.

namestring

A human-readable name of the item's type, suitable for display.

This string is translated using the installation's language pack, and Gallagher reserves the right to change item type names in new versions, so you should not do anything with this string except show it to a person.

nextobject

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

Show child attributes
hrefstring<uri-reference>
previousobject

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

Show child attributes
hrefstring<uri-reference>
updatesobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An alphanumeric identifier for this event, unique to the server.

serverDisplayNamestring

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

messagestring

The event's message.

occurrencesinteger>= 2

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

alarmobject

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

Show child attributes
statestringunacknowledgedacknowledgedprocessed

Alarms start unacknowledged. Acknowledging or processing them changes that.

hrefstring<uri-reference>

Link to the alarm entity corresponding to the event. GET the href for full alarm details.

operatorobject

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

Show child attributes
hrefstring<uri-reference>

Link to the operator who caused this event by editing an item. New in 8.00.

namestring

The name the operator held at the time. New in 8.40.

sourceobject

ID and name of the source of the event, as recorded at the time of the event.

Show child attributes
idstring

The alphanumeric ID of the event source item. Search for events with the same source as this one with source=321 in the query parameters.

namestring

This could be different from the current name of the source item.

hrefstring<uri-reference>

Link to the source item. New in 8.00.

groupobject

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

Show child attributes
idstring

The alphanumeric ID of the event group. Search for events of the same rough category as this one with type=35 in the query parameters.

namestring

The name of the event group.

typeobject

ID and name of the event's type. There is a long list of them at /events/groups.

Show child attributes
idstring

The alphanumeric ID of the event type. To search for events of the same type as this example, put type=23035 in the query parameters of /events.

namestring

The name of the event type.

eventTypeobject

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

divisionobject

ID, name, and href of the event's division (which is the division of the event's source item, for most event types).

Show child attributes
idstring

The alphanumeric ID of the event's division. Search for other events from items in the same division as this example by putting division=2 in the query parameters.

hrefstring<uri-reference>

The link to the division item.

namestring

The division's name. Added in 8.40.

cardholderobject

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

Show child attributes
hrefstring<uri-reference>

Link to the cardholder entity representing the cardholder of the event. GET the href for full details.

idstring

The alphanumeric ID of the cardholder associated with this event.

namestring

In versions up to and including 8.10 this is the current name of the cardholder. In 8.20 it is the name of the cardholder at the time of the event.

firstNamestring

The current value of the firstName field of this cardholder. Added in 8.20.

lastNamestring

The current value of the lastName field of this cardholder. Added in 8.20.

entryAccessZoneany

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

Show child attributes
hrefstring<uri-reference>

Link to the access zone entity representing the entry access zone related to this event. GET the href for the access zone's full details. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The current name of the access zone. Expect a future version of Command Centre to change this to change to the name of the access zone at the time of the event.

idstring

Deprecated.

exitAccessZoneany

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

Show child attributes
hrefstring<uri-reference>

Link to the access zone entity representing the exit access zone related to this event. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The current name of the exit access zone. Expect a future version of Command Centre to change this to change to the name of the exit access zone at the time of the event.

idstring

Deprecated.

doorany

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

Show child attributes
hrefstring<uri-reference>

Link to the entity representing the door related to this event. This will be missing if the server lacks the RESTStatus licence, or your operator lacks the necessary privileges (such as 'View Site').

namestring

The name the door had when the event occurred.

accessGroupobject

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

Show child attributes
hrefstring<uri-reference>

Link to the access group related to this event.

cardobject

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

Show child attributes
facilityCodestring

The card's facility code at the time of the event expressed as one letter followed by up to five digits.

numberstring

The card's number at the time of the event. Despite the name it may not necessarily be an actual number; mobile card numbers are arbitrary strings, for example.

Note that card numbers are not guaranteed unique. The combination of facility code, issue level, and card number will be unique for card types that have a facility code and issue level.

Before 8.60 this number was a signed 32-bit integer, so numeric card numbers greater than 2^31 came out negative and mobile card numbers were arbitrary. In 8.60 numeric card numbers are positive and mobile card numbers are their ID strings, which operators can set to anything, and default to GUIDs.

issueLevelinteger

The issue level of the card at the time.

modifiedItemobject

The href and type of the item that an operator created, changed, or deleted, for those kinds of events.

New in 8.40.

Show child attributes
hrefstring<uri-reference>

Link to the item that this event modified. Watch for 404s: this link will be here even for deleted items.

typeobject
Show child attributes
idstring

A short alphanum identifying the item's type.

namestring

A human-readable name of the item's type, suitable for display.

This string is translated using the installation's language pack, and Gallagher reserves the right to change item type names in new versions, so you should not do anything with this string except show it to a person.

nextobject

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

Show child attributes
hrefstring<uri-reference>
previousobject

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

Show child attributes
hrefstring<uri-reference>
updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

detailsstring

The full alarm details text. This may be up to 2048 UTF-8 characters, each of which could (theoretically) be four bytes long.

locationobject

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

Show child attributes
typestringmovedobserveddenied

This enum divides location events into three types:

moved means a cardholder moved through a door from one zone to another. Whether an operator moved them using our software, or they did it themselves using a plastic card, a mobile credential, biometrics, a QR code, a vehicle plate, or something implemented by a third-party integration, provided they were granted access, this field's value will be moved.

observed means they did something that indicated their location, such as logging in to a workstation or an alarms terminal, but they did not move.

denied means the cardholder authenticated successfully and requested access at a door but failed the access check. It also appears on some event types where the cardholder did not successfully authenticate; for those events there will be no cardholder block, covered next.

cardholderobject

A block describing the cardholder whose location is recorded by the event.

It will be missing if the cardholder did not successfully authenticate: perhaps they used their card but did not complete a second factor. In most cases, though, if a cardholder does not authenticate properly (gets their PIN wrong, for example) there will be no location block at all.

Show child attributes
namestring

The cardholder's name at the time of the event.

hrefstring<uri-reference>

The cardholder item's href. This will only be present if the cardholder has not been deleted and your operator has the right to view it.

beforeLocationany

The href and name of the access zone or reception that the cardholder was in before this event occurred. Future versions of Command Centre may add more item types as "before locations" so check its canonical type name, covered in the afterLocation description below.

The before location is only useful (and present) for 'moved'-type events, which happen when a cardholder changes location. For 'observed' and 'denied'-type events, which happen when a cardholder does not move, afterLocation shows where they were.

The name was correct at the time of the event. It may be missing.

The href will be missing if your operator does not have the right to view the location. That also happens when it is deleted.

If the cardholder moved from outside there will be a field in this block called outside with the value true, and canonicaltypeName and href will be missing.

afterLocationany

The href and name of the item that indicates the cardholder's location after the event. For 'observed' and 'denied'-type events, this is also where they started.

For 'moved' and 'denied' events in 9.00 it will be an access zone or reception.

For 'observed' events it could be a workstation or an alarms terminal at which the person authenticated.

Future versions of Command Centre may add more item types. So that you can branch on them, this block and beforeLocation contain a field called canonicalTypeName. The values it returns are fixed: it is safe to make decisions based on the value. We will add new values as Command Centre grows, but after its first appearance an item type's canonicalTypeName will not change.

Common canonical type names are accesszone, reception, workstation, and (for a T20 alarms terminal that uses the H-Bus protocol) hbusterminal. You can get the full list with the item types.

If they moved "outside the system" (entering through a door with no entry zone, or exiting through a door with no exit zone), there will be no href or canonical type, but there will be a field called outside with the value true.

The href will also be missing if your operator does not have the right to view their new location.

If present, the item's name was correct at the time of the event. It may have changed since. It will be missing for some event types such as 15583, which happens when an operator moves a cardholder outside the system using an operational client.

Example
{
  "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

object
typeobjectdeprecated

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

Show child attributes
hrefstring<uri-reference>
eventTypeanyrequired

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

Show child attributes
hrefstring<uri-reference>

Take this href from the list of event types. Note that you can only use event types in one of the thirty external event groups with IDs 57-66 and 190-209. Command Centre ships with one event type per group, IDs 4000-4009 and 6010-6029, but you can create 970 more using the External Event Type Configuration utility.

sourceobject

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

Show child attributes
hrefstring<uri-reference>

Get the href using the API controller for that type of item (such as doors) or from the more general item search.

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

messagestring

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

detailsstring

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

cardholderobject

If you wish to attach a cardholder to your event, link it here. Reports can show or filter by the cardholder.

Show child attributes
hrefstring<uri-reference>

This can be the href of any cardholder to which your operator has view access. Get the href from the cardholders controller.

operatorobject

If you wish to attach an operator to your event, link it here. Like the cardholder, reports can show or filter by the operator.

Show child attributes
hrefstring<uri-reference>

This can be the href of any cardholder to which your operator has view access. It does not need to be an operator (a member of an operator group).

entryAccessZoneobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any access zone to which your operator has view access. Get the href using the access zones controller or from /items.

accessGroupobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any access group to which your operator has view access. Get the href using the groups controller or the items controller.

lockerBankobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any locker bank to which your operator has view access. Get the href using the locker banks calls or the more general items routes.

lockerobject

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

Show child attributes
hrefstring<uri-reference>

This can be the href of any locker your operator can view. Get the href from the items controller or the lockers field of a locker bank.

doorobject

If you wish to link a door to your event for later extraction or a report filter, do it here.

Show child attributes
hrefstring<uri-reference>

This can be the href of any door your operator can see. Get it with a doors call or a more general items search.

Example
{
  "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

object

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

Show child attributes
idstring

The alphanumeric ID of the event group. Use this ID in the group filter when requesting events.

namestring

The name of the event group.

eventTypesArray<object>

An array of all the event types in the group.

Show child attributes
hrefstring<uri-reference>
idstring

The alphanumeric ID of the event type. Use this ID in the type filter when requesting events.

namestring
Example
{
  "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

object

An array of fence zone summaries, and a next link for more.

resultsArray<FenceZoneSummary>

An array of Fence Zone summaries.

Show child attributes
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.

idstring

An 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
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this Fence Zone.

voltageinteger>= 0

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

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

commandsobject

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

Show child attributes
onobject
Show child attributes
hrefstring<uri-reference>

POST to this to energise this fence zone.

offobject
Show child attributes
hrefstring<uri-reference>

POST to this to deactivate this fence zone.

shuntobject
Show child attributes
hrefstring<uri-reference>

POST to this to shunt this fence zone.

unshuntobject
Show child attributes
hrefstring<uri-reference>

POST to this to restore communication with fence zone.

highVoltageobject
Show child attributes
hrefstring<uri-reference>

POST to this to set this fence zone to 'high voltage' mode.

lowFeelobject
Show child attributes
hrefstring<uri-reference>

POST to this to set this fence zone to 'low feel' mode.

cancelobject
Show child attributes
hrefstring<uri-reference>

POST to this cancel an active override on the fence zone.

Example
{
  "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

object

An array of input summaries, and a next link for more.

resultsArray<InputSummary>

An array of input summaries.

Show child attributes
hrefstring<uri-reference>

A link to an input detail object for this input.

idstring

An 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
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this input.

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

commandsobject

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

Show child attributes
shuntobject
Show child attributes
hrefstring<uri-reference>

POST to this to shunt the input.

unshuntobject
Show child attributes
hrefstring<uri-reference>

POST to this to stop shunting the input.

isolateobject
Show child attributes
hrefstring<uri-reference>

POST to this to isolate the input. Isolated inputs do not prevent alarm zones from arming.

The link will be missing if the input is shunted.

deisolateobject
Show child attributes
hrefstring<uri-reference>

POST to this to stop isolating the input. The link will be missing if the input is shunted.

Example
{
  "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

object

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

Show child attributes
hrefstring<uri-reference>

A link to an interlock group detail object for this item.

namestring
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "results": [
    {
      "href": "https://localhost:8904/api/interlock_group/122322",
      "name": "Excercise yard egress"
    }
  ],
  "next": {
    "href": "https://localhost:8904/api/interlock_groups?skip=1000"
  }
}

InterlockGroupSummary

object

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.

namestring
Example
{
  "href": "https://localhost:8904/api/interlock_group/122322",
  "name": "Excercise yard egress"
}

InterlockGroupDetail

object

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

namestring
idstring

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

descriptionstring
divisionobject

The division containing this interlock group.

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

statusTextstring

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

statusstring

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

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

commandsobject

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

Show child attributes
disableobject
Show child attributes
hrefstring<uri-reference>

POST to this to disable the interlock group, allowing the doors to operate independently.

enableobject
Show child attributes
hrefstring<uri-reference>

POST to re-enable the interlock group, causing its doors to return to interlock behaviour.

Example
{
  "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

object

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

Show child attributes
idstring

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

namestring
shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

descriptionstring

In 9.60 and later.

typeobject

The type object contains the ID and name of the item's type.

Show child attributes
idstring

Use this ID in the type filter when requesting items if you wish to restrict the results to items of a certain type.

namestring

A human-readable name of the item's type, suitable for display.

This string is translated using the installation's language pack, and Gallagher reserves the right to change item type names in new versions, so you should not do anything with this string except show it to a person.

canonicalTypeNamestring

An unchanging, alphanumeric, short, and hopefully descriptive identifier for the item type.

The set of possible values will grow as time passes but the strings themselves, unless highlighted as temporary, will never change. You can count on an access zone always having a canonical type name of 'accesszone', for example.

Added in 9.00.

serverDisplayNamestringread only

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

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

hrefstring

Reserved for internal use. Its value changed in version 9.10.

nextobject

An href to the next page of results. Missing if there are no more results.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

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

namestring
shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

descriptionstring

In 9.60 and later.

typeobject

The type object contains the ID and name of the item's type.

Show child attributes
idstring

Use this ID in the type filter when requesting items if you wish to restrict the results to items of a certain type.

namestring

A human-readable name of the item's type, suitable for display.

This string is translated using the installation's language pack, and Gallagher reserves the right to change item type names in new versions, so you should not do anything with this string except show it to a person.

canonicalTypeNamestring

An unchanging, alphanumeric, short, and hopefully descriptive identifier for the item type.

The set of possible values will grow as time passes but the strings themselves, unless highlighted as temporary, will never change. You can count on an access zone always having a canonical type name of 'accesszone', for example.

Added in 9.00.

serverDisplayNamestringread only

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

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

hrefstring

Reserved for internal use. Its value changed in version 9.10.

Example
{
  "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

object

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

idstring

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

namestring
shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

descriptionstring

In 9.60 and later.

typeobject

The type object contains the ID and name of the item's type.

Show child attributes
idstring

Use this ID in the type filter when requesting items if you wish to restrict the results to items of a certain type.

namestring

A human-readable name of the item's type, suitable for display.

This string is translated using the installation's language pack, and Gallagher reserves the right to change item type names in new versions, so you should not do anything with this string except show it to a person.

canonicalTypeNamestring

An unchanging, alphanumeric, short, and hopefully descriptive identifier for the item type.

The set of possible values will grow as time passes but the strings themselves, unless highlighted as temporary, will never change. You can count on an access zone always having a canonical type name of 'accesszone', for example.

Added in 9.00.

serverDisplayNamestringread only

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

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

hrefstring

Reserved for internal use. Its value changed in version 9.10.

divisionobject
Show child attributes
idstring
hrefstring<uri-reference>
Example
{
  "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

object

Calls to /api/items/types return this object, an array of item types.

itemTypesArray<object>

A list of item types.

Show child attributes
idstring

The alphanumeric ID of the item type. Use this ID in the type filter when requesting items.

namestring

The human-readable name of the item type. These strings will change over time as Gallagher expands its product line, and can change with the server's language settings, so take care if using them for anything except display.

canonicalTypeNamestring

An unchanging, alphanumeric, short, and hopefully descriptive identifier for the item type.

The set of possible values will grow as time passes but the strings themselves, unless highlighted as temporary, will never change. You can count on an access zone always having a canonical type name of 'accesszone', for example.

Added in 9.00.

Example
{
  "itemTypes": [
    {
      "id": "1",
      "name": "Cardholder",
      "canonicalTypeName": "cardholder"
    },
    {
      "id": "2",
      "name": "Access Group",
      "canonicalTypeName": "accessgroup"
    }
  ]
}

ItemUpdate

object

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

Show child attributes
idstring

The item's ID, from the list you sent in the POST that created this subscription.

statusFlagsArray<string>

An array of string enumerations (flags) that describe the item's condition in a reliable, machine-readable way.

The item types that the REST API supports (such as fence zones and inputs) have a full set of status flags, described in their own sections of this documentation. To see the values that this enumeration can take, search for headings with 'status flags' in their name in those sections.

Items that the REST API does not support yet (such as readers) will return error flags if they are in an unusual state, or nothing if they are online and reporting normally. Therefore an empty array is a good sign.

statusTextstring

The state of the item in a multi-line string taken from the server's language pack.

statusstring

A one-line version of the status, with details removed if necessary to keep it short.

nextany

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

Example
{
  "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

object

An array of locker bank summaries, and a next link for more.

resultsArray<LockerBankSummary>

An array of locker summaries.

Show child attributes
hrefstring<uri-reference>

A link to the detail page for this locker bank.

namestring
shortNamestring

The bank's short name.

descriptionstring
divisionobject

The division containing this locker bank.

nextobject

The link to the next page of results. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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

namestring
shortNamestring

The bank's short name.

descriptionstring
divisionobject

The division containing this locker bank.

Example
{
  "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.

namestring
shortNamestring
descriptionstring
divisionobject

The division containing this locker bank.

notesstring
connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

lockersArray<object>

An array of locker objects, each containing the details of the locker and the cardholders who can open it.

Show child attributes
namestring
shortNamestring

A T20 will display this instead of the long name, if it has both.

descriptionstring
hrefstring<uri-reference>

This is the href of the locker. Use it when creating new assignments in a cardholder PATCH or POST. Trying to GET it will always yield a 404.

connectedControllerobject

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

Show child attributes
hrefstring<uri-reference>

This is the href of a cardholder's assignment to a locker. Put it in a cardholder PATCH to update or delete it.

You can also revoke a cardholder's access to this locker by sending a DELETE to this href.

If the site does not have the RESTCardholders licence or your operator does not have the privilege to view the cardholder, this link will not work in a DELETE or a cardholder PATCH.

cardholderobject

The name and href of the cardholder assigned to this locker. It will be missing if the site does not have the RESTCardholders licence or your operator does not have the privilege to view that cardholder.

fromstring<date-time>

If missing, the locker assignment has no start time, meaning the cardholder can open the locker provided the 'until' time is in the future.

untilstring<date-time>

If missing, the locker assignment has no end time, meaning the cardholder can open the locker provided the 'from' time is in the past.

commandsobject

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

Show child attributes
openobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send an open override to the locker. The server will ignore the POST body.

quarantineobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send a quarantine override to the locker. Do not send a body with the POST since some server versions use the same POST for quarantineUntil, which takes a time from the body.

quarantineUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send an override to the locker that will quarantine it until a timestamp you send in the body.

cancelQuarantineobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send a quarantine cancel override to the locker. The server will ignore the POST body.

updatesany

This is not a default field: you must ask for it by adding lockers.updates to the fields query parameter. Since it can only monitor one locker at a time we discourage its use in favour of bulk status monitoring.

Example
{
  "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.

namestring
shortNamestring
descriptionstring
divisionobject

The division containing this locker.

notesstring

The notes field is not in the default result set. You must ask for it using fields.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

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.

Show child attributes
hrefstring<uri-reference>

This is the href of a cardholder's assignment to a locker. Put it in a cardholder PATCH to update or delete it.

You can also revoke a cardholder's access to this locker by sending a DELETE to this href.

If the site does not have the RESTCardholders licence or your operator does not have the privilege to view the cardholder, this link will not work in a DELETE or a cardholder PATCH.

cardholderobject

The name and href of the cardholder assigned to this locker. It will be missing if the site does not have the RESTCardholders licence or your operator does not have the privilege to view that cardholder.

fromstring<date-time>

If missing, the locker assignment has no start time, meaning the cardholder can open the locker while the 'until' time is in the future.

untilstring<date-time>

If missing, the locker assignment has no end time, meaning the cardholder can open the locker after the 'from' time.

commandsobject

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

Show child attributes
openobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send an open override to the locker. The server will ignore the POST body.

quarantineobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send a quarantine override to the locker. Do not send a body with the POST since some server versions use the same POST for quarantineUntil, which takes a time from the body.

quarantineUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send an override to the locker that will quarantine it until a timestamp you send in the body.

cancelQuarantineobject
Show child attributes
hrefstring<uri-reference>

POST to this URL to send a quarantine cancel override to the locker. The server will ignore the POST body.

updatesobject

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

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

An array of macro summaries, and a next link for more.

resultsArray<MacroSummary>

An array of macro summaries.

Show child attributes
hrefstring<uri-reference>

A link to a macro detail object for this macro.

idstring

An 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
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this Macro.

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

commandsobject

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

Show child attributes
runobject
Show child attributes
hrefstring<uri-reference>

POST to this to run this macro.

Example
{
  "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

object

An array of operator group summaries, described in the next section, and a next link for more.

resultsArray<OperatorGroupSummary>

An array of operator group summaries.

Show child attributes
hrefstring<uri-reference>

A link to an operator group detail object for this operator group.

namestring
serverDisplayNamestringread only

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

nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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

object

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.

namestring
serverDisplayNamestringread only

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

Example
{
  "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.

namestring
serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division that contains this operator group. This has no bearing on the divisions to which this operator group grants privileges: that is divisions.

cardholdersobject

Following this link lists the group's cardholder members.

divisionsArray<object>

An array containing the divisions in which this operator group grants its privileges.

Show child attributes
divisionobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

Example
{
  "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

object

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

cardholderobject

The name and href of the member cardholder.

Example
{
  "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD",
  "cardholder": {
    "name": "Boothroyd, Algernon",
    "href": "https://localhost:8904/api/cardholders/325"
  }
}

OutputSearch

object

An array of output summaries, and a next link for more.

resultsArray<OutputSummary>

An array of output summaries.

Show child attributes
hrefstring<uri-reference>

A link to an output detail object for this output.

idstring

An 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
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

idstring

An 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
Example
{
  "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.

idstring

An 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
descriptionstring
divisionobject

The division containing this output.

shortNamestring

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

?fields=shortname,....

notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

updatesobject

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

Show child attributes
hrefstring<uri-reference>
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.

connectedControllerobject

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

Show child attributes
namestring
hrefstring<uri-reference>

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because there is no interface for hardware controllers. GETting the URL will return a 404.

idstring

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of event filters.

commandsobject

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

Show child attributes
onobject
Show child attributes
hrefstring<uri-reference>

POST to this to close this output.

onUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to close this output for a fixed time.

offobject
Show child attributes
hrefstring<uri-reference>

POST to this to open this output.

offUntilobject
Show child attributes
hrefstring<uri-reference>

POST to this to open this output for a fixed time.

pulseobject
Show child attributes
hrefstring<uri-reference>

POST to this to close this output for its pulse time.

This link will only be here if the output is set to pulse when activated.

Added in 8.50.

Example
{
  "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

object

An array of PDF definition summaries, and a next link for more.

resultsArray<PDFDefinition>

An array of PDF definition summaries.

Show child attributes
idstringread only

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

namestring
serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this PDF definition. Required when creating one.

typestringstringimagestrEnumnumericdateaddressphoneemailmobile

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

defaultstring

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

requiredbooleanfalse

If true, every cardholder with this PDF must have a value for it. No blanks allowed.

uniquebooleanfalse

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

defaultAccessstringnoAccessreadOnlyfullAccessfullAccess

This 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 only

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

sortPriorityinteger

This 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 only

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

Show child attributes
namestring
hrefstring<uri-reference>
notificationDefaultboolean

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

regexstring

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

regexDescriptionstring

Regular expressions often need explaining.

imageWidthinteger

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

imageHeightinteger

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

imageFormatstringbmpjpgpngdeprecated

Whether 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/png

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

isProfileImageboolean

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

nextobject

The link to the next page of results. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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 only

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

namestring
serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this PDF definition. Required when creating one.

typestringstringimagestrEnumnumericdateaddressphoneemailmobile

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

defaultstring

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

requiredbooleanfalse

If true, every cardholder with this PDF must have a value for it. No blanks allowed.

uniquebooleanfalse

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

defaultAccessstringnoAccessreadOnlyfullAccessfullAccess

This 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 only

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

sortPriorityinteger

This 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 only

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

Show child attributes
namestring
hrefstring<uri-reference>
notificationDefaultboolean

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

regexstring

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

regexDescriptionstring

Regular expressions often need explaining.

imageWidthinteger

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

imageHeightinteger

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

imageFormatstringbmpjpgpngdeprecated

Whether 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/png

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

isProfileImageboolean

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

Example
{
  "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

object

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

hashstringrequired

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

fascnstringrequired

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

orgIdentifierstring

Optional.

dunsstring

Optional.

Example
{
  "hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
  "fascn": "47000256001337111234567890199991",
  "orgIdentifier": "",
  "duns": ""
}

NewPIVCard

object

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

typeobjectrequired
Show child attributes
hrefstring<uri-reference>

The href of the server's PIV card type. This documentation has a section called "finding the PIV card type" if you need it.

statusobject
Show child attributes
valuestring
numberstringrequired

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

pivDataobjectrequired

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

Show child attributes
chuidCHUIDrequired

Required when creating a card, ignored when updating one.

The FASC-N in the chuid block, and its relationship with the card number, is the only difference between PIV and PIV-I cards in Command Centre.

Show child attributes
hashstringrequired

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

fascnstringrequired

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

orgIdentifierstring

Optional.

dunsstring

Optional.

pivStatusobject

The PIV status held in the type field in this block is distinct from the generic card status, which is a different field outside the pivData block.

It is optional. If you omit it when creating a card, the card will be enabled with a status of "NotChecked", which—despite how it sounds—is a perfectly valid operating status. If Command Centre is doing periodic certificate validation, the status will eventually change (to 'normal', all going well).

You should send this block when creating a card only if you are doing your own certificate validation.

When updating an existing card, this is the only field it makes sense to send (specifically, the type field inside it).

type must be set to one of these values, based on the result of your validation:

  • Normal
  • Offline
  • Revoked
  • Expired
  • CertInChainRevoked
  • CertInChainExpired
  • IssuerSigCertRevoked
  • IssuerSigCertExpired
  • NotTrusted
  • PolicyError
  • OtherError
  • NotChecked

If you set it to any value except 'NotChecked, 'Normal', or 'Offline', Command Centre will consider the card invalid and deactivate it regardless of what you pass as the card status. You, or Command Centre's periodic certificate validation, may validate the card again later.

Show child attributes
typestringNormalOfflineRevokedExpiredCertInChainRevokedCertInChainExpiredIssuerSigCertRevokedIssuerSigCertExpiredNotTrustedPolicyErrorOtherErrorNotChecked
contentSigningCertstringrequired

This is required when creating a PIV or PIV-I card. The API will reject your request if this is missing or not a Base64-encoded certificate, but it will not validate the certificate itself.

This example is shortened to fit on screen. Real certificates are at least a thousand characters.

cardAuthenticationCertstring

This contains the CAK, which is necessary for the secure use of contactless cards. It is not required for contact cards.

Optional when creating a PIV or PIV-I card. The API will reject your request if this is present and not a Base64-encoded certificate, but it will not validate the certificate itself.

pivAuthenticationCertstringrequired

This is required when creating a PIV or PIV-I card. The API will reject your request if this is missing or not a Base64-encoded certificate, but it will not validate the certificate itself.

fingerprintsstring

If you send this when creating a card, it should be the cardholder's fingerprints contained in the card's Cardholder Fingerprints data object with the error detection code removed: the whole CBEFF structure including the CBEFF_HEADER, CBEFF_BIOMETRIC_RECORD, and CBEFF_SIGNATURE_BLOCK components. Refer to Section 9 of NIST Special Publication 800-76-2: Biometric Data Specification for Personal Identity Verification.

It is optional. The API will check that this is Base64, but will not verify that it is valid biometric data.

Example
{
  "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

object

An array of receptions, and a next link for more.

resultsArray<Reception>

An array of receptions.

Show child attributes
namestring
hrefstring<uri-reference>

This is the href to use when creating a visit.

serverDisplayNamestringread only

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

descriptionstring

Short free-form text. Searches do not return an item's description - ask for it using the fields parameter.

divisionobject

The division containing this reception.

defaultVisitorTypeobject

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

notesstring

Free-form text. You will only get this field if you ask for it with the fields parameter.

nextobject

The link to the next page of results. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "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.

namestring
hrefstring<uri-reference>

This is the href to use when creating a visit.

serverDisplayNamestringread only

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

descriptionstring

Short free-form text. Searches do not return an item's description - ask for it using the fields parameter.

divisionobject

The division containing this reception.

defaultVisitorTypeobject

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

notesstring

Free-form text. You will only get this field if you ask for it with the fields parameter.

Example
{
  "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

object

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

typestringnormalEventscardholder

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

statusstringpendinginProgresscancelleddonefailed

The status of this redaction.

redactionOperatorobject

A block containing the href and current name of the operator who scheduled the redaction.

cardholderobject

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

messagestring

Translated string from the redaction's error code. Will be absent if empty, or if the redaction is pending or complete.

detailsstring

A more detailed description of what went wrong.

Not translated. Will be absent if empty, or if the redaction is pending or complete.

Example
{
  "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

object

POST one of these to schedule a redaction.

These fields are common to redaction POSTs and GETs.

cardholderobject

The href of the cardholder whose events or item this redaction should affect.

Required.

typestringnormalEventscardholder

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

Example
{
  "cardholder": {
    "href": "https://localhost:8904/api/cardholders/630"
  },
  "type": "normalEvents",
  "before": "2022-01-01T00:00:00Z",
  "when": "2023-01-01T00:00:00Z"
}

RedactionCommonFields

object

These fields are common to redaction POSTs and GETs.

cardholderobject

The href of the cardholder whose events or item this redaction should affect.

Required.

typestringnormalEventscardholder

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

Example
{
  "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.

namestring
hrefstring<uri-reference>read only

This is the string to use when creating a relationship between cardholders using this role.

serverDisplayNamestringread only

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

descriptionstring
divisionobject

The division containing this role. Required in a POST, optional in a PATCH.

enableCompetencyExpiryWarningsboolean

Controls whether CC sends notifications to a person holding this role when their staff's competencies are about to expire.

enableCardExpiryWarningsboolean

Controls whether CC sends notifications to a person holding this role when their staff's cards are about to expire.

Example
{
  "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

object

An array of schedule summaries, and a next link for more.

resultsArray<ScheduleSummary>

An array of schedule summaries.

Show child attributes
hrefstring<uri-reference>

A link to a schedule detail.

namestring
nextobject

The link to the next page. Absent if you have retrieved them all.

Show child attributes
hrefstring<uri-reference>
Example
{
  "results": [
    {
      "href": "https://localhost:8904/api/schedules/6",
      "name": "Default Access Zone Secure"
    }
  ],
  "next": {
    "href": "https://localhost:8904/api/schedules?skip=1000"
  }
}

ScheduleSummary

object

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.

namestring
Example
{
  "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.

namestring
descriptionstring
divisionobject

The division containing this schedule. It is possible to change a schedule's division after creating it.

Show child attributes
hrefstring<uri-reference>
notesstring

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

?fields=defaults,notes,...

typeobject

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

Show child attributes
typestringaccessZoneScheduleaccessSchedulealarmZoneScheduleoutputSchedulenotificationSchedulehVLFScheduleelevatorKioskControlSchedule
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 time field of each object inside times must be a string of the form HH:MM between 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 grant and deny. 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, or free. The extra flag usePin means 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, and user2. Use the words user1 and user2 here even if you have renamed them in the server properties.

  • An Output Schedule can be on or off, 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 notificationEnabled and notificationDisabled.

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

Show child attributes
dayCategoryobject
Show child attributes
namestringread only
hrefstring<uri-reference>

The API's identifier for an item.

timesArray<object>
Show child attributes
timestring
stateArray<string>
scheduledItemsArray<object>read only

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

Show child attributes
hrefstring<uri-reference>
namestring
Example
{
  "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

object

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.

typestringnormalEventscardholder

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

statusstringpendinginProgresscancelleddonefailed

The status of this redaction.

redactionOperatorobject

A block containing the href and current name of the operator who scheduled the redaction.

Example
{
  "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

object

GET /api/visits returns an array of these (with fewer fields) and GET /api/visits/{id} returns one.

namestring

You 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 only

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

descriptionstring

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

divisionobject

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

receptionobject

Every 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"

Show child attributes
namestring
hrefstring<uri-reference>
visitorTypeobject

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

Show child attributes
hrefstring<uri-reference>
accessGroupobject
Show child attributes
namestring
hrefstring<uri-reference>
hostobject

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

Show child attributes
namestring
hrefstring<uri-reference>
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.

locationstring

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

Show child attributes
namestring
hrefstring<uri-reference>
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.

Show child attributes
cardholderobject

A block containing the name and cardholder href of the visitor.

Show child attributes
namestring
hrefstring<uri-reference>
hrefstring<uri-reference>

An href unique to the pairing of this cardholder and this visit. You can PATCH it to change the visitor's state.

New to 8.90.

invitationstring

A blob of text which, when encoded into a QR code and presented to a Gallagher Visitor Management kiosk, allows the visitor to sign in.

This does not appear unless you ask for it using the fields query parameter. Because it is inside the visitors block, name it visitors.invitation in the field list.

statusobject

The cardholder's status on this visit, in human-readable and machine-readable forms.

Also new to 8.90.

Show child attributes
valuestring

A human-readable version of the visitor's status, varying with the version of Command Centre and the server's locale.

typestringexpectedsigningInsignedInonSiteexpectedBackdepartedcancelled

The visitor's current state taken from a fixed list of strings. New visitors have a starting state of 'expected'.

Example
{
  "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"
    }
  ]
}