diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb1be59 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Gallagher Command Centre REST API + +In 2020 this Github repository became the authoritative source of the reference documentation for +Command Centre's REST API. + +The `ref` folder of this repository contains the documentation in HTML form. You can browse it +online at [github.io]. + +We use [Spectacle](https://github.com/sourcey/spectacle) to generate that HTML from OpenAPI/Swagger +2.0 files in the `swagger` folder. Generating good-looking HTML is the sole purpose of those +Swagger files: we do not use them for code generation or API testing. Therefore they do not follow +OpenAPI guidelines where doing so would compromise the readability of the documentation. + +To download the HTML and YAML in a zip file use the Code button on +[Github.com](https://github.com/GallagherSecurity/cc-rest-docs). However, to benefit from our +regular improvements to that documentation we suggest you browse [the HTML][github.io] whenever +possible. + +[github.io]: https://gallaghersecurity.github.io/cc-rest-docs/ref diff --git a/api-licensing-860.html b/api-licensing-860.html new file mode 100644 index 0000000..fcfbca9 --- /dev/null +++ b/api-licensing-860.html @@ -0,0 +1,298 @@ + + + + Command Centre REST API URLs and Licensing + + + + + +

Command Centre REST API URLs and Licensing

+

Command Centre 8.60

+
+ +
+

License Required:

+
Any
+
RESTCardholders
+
RESTCardholders and VisitorManagement
+
RESTStatus
+
RESTOverrides
+
RESTStatus or RESTOverrides
+
RESTEvents
+
RESTCreateEvents
+
+ +
+
GET /api - API entrypoint
+ +
 
+ +
GET /api/items - generic item search
+
GET /api/items/{id} - load individual item (generic fields only)
+
GET /api/items/types - list item types
+
GET|POST /api/items/updates - bulk status monitoring for multiple items
+ +
 
+ +
GET /api/divisions/{id} - load individual item
+
GET /api/divisions/view_alarms - list divisions in which we can view alarms
+
GET /api/divisions/view_events - list divisions in which we can view events
+
GET /api/divisions/view_access_zones - list divisions in which we can view access zones
+
GET /api/divisions/view_alarm_zones - list divisions in which we can view alarm zones
+
GET /api/divisions/view_doors - list divisions in which we can view doors
+
GET /api/divisions/view_fence_zones - list divisions in which we can view fence zones
+
GET /api/divisions/view_readers - list divisions in which we can view readers
+
GET /api/divisions/view_macros - list divisions in which we can view macros
+
GET /api/divisions/view_fence_zones - list divisions in which we can view fence zones
+ +
 
+ +
GET /api/cardholders - search
+
GET /api/cardholders/changes - track changes across multiple cardholders
+
GET /api/cardholders/thumbnails - bulk thumbnail images for cardholder photos
+
POST /api/cardholders - create a cardholder
+
GET /api/cardholders/{id} - load individual item
+
POST|PATCH /api/cardholders/{id} - update individual cardholder
+
DELETE /api/cardholders/{id} - delete individual cardholder
+
POST /api/cardholders/{id}/update_location - update a cardholder's last known location
+
GET /api/cardholders/{id}/edit - metadata for interactive editing
+
GET /api/cardholders/{id}/personal_data/{pdfId} - get an specific personal data item
+
GET /api/cardholders/{id}/updates - monitor for status changes
+
DELETE /api/cardholders/{id}/roles/{roleId} - remove a role from a cardholder
+
DELETE /api/cardholders/{id}/competencies/{competencyId} - remove a competency from a cardholder
+
POST /api/cardholders/{id}/competencies/{competencyId}/credit - adjust competency credit
+
DELETE /api/cardholders/{id}/cards/{cardId} - remove a card from a cardholder
+
DELETE /api/cardholders/{id}/access_groups/{groupId} - remove an access group from a cardholder
+
DELETE /api/cardholders/{id}/operator_groups/{groupId} - remove an operator group from a cardholder
+
DELETE /api/cardholders/{id}/lockers/{assignmentId} - remove a locker assignment from a cardholder
+
DELETE /api/cardholders/{id}/elevator_groups/{groupId} - remove an elevator group assignment from a cardholder
+ +
 
+ +
GET /api/personal_data_fields/view - list pdfs an operator could assign based on view privileges
+
GET /api/personal_data_fields/edit - list pdfs an operator could assign based on edit privileges
+
GET /api/personal_data_fields - search
+
GET /api/personal_data_fields/{id} - load individual item
+ +
 
+ +
GET /api/access_groups - search
+
GET /api/access_groups/{id} - load individual item
+
GET /api/access_groups/tree - load access group tree
+
GET /api/access_groups/{id}/cardholders - list direct cardholder members
+ +
 
+ +
GET /api/card_types - search
+
GET /api/card_types/{id} - load individual item
+
GET /api/card_types/assign - list all assignable card types
+ +
 
+ +
GET /api/competencies - list
+
GET /api/competencies/{id} - load individual item
+ +
 
+ +
GET /api/schedules - list
+
GET /api/schedules/{id} - load individual item
+
PATCH /api/schedules/{id} - update a schedule
+
DELETE /api/schedules/{id} - delete a schedule
+
POST /api/schedules - create a schedule
+ +
 
+ +
GET /api/day_categories - list
+
GET /api/day_categories/{id} - load individual item
+ +
 
+ +
GET /api/lockers - list
+
GET /api/lockers/{id} - load individual item
+
POST /api/lockers/{id}/open - override open a locker
+ +
GET /api/locker_banks - list
+
GET /api/locker_banks/{id} - load individual item
+ +
 
+ +
GET /api/operator_groups - list
+
GET /api/operator_groups/{id} - load individual item
+
GET /api/operator_groups/{id}/cardholders - list cardholders assigned to this group
+ +
 
+ +
GET /api/roles - list
+
GET /api/roles/{id} - load individual item
+ +
 
+ +
GET /api/receptions - list
+
GET /api/receptions/{id} - load individual item
+ +
 
+ +
GET /api/visits - list
+
GET /api/visits/{id} - load individual item
+
POST /api/visits - create a visit
+
PATCH /api/visits/{id} - update a visit
+ + +
 
+ +
GET /api/alarms - list alarms
+
GET /api/alarms/updates - monitor for alarm changes
+
GET /api/alarms/{id} - load individual item
+
POST /api/alarms/{id}/view - generate audit record that alarm has been viewed
+
POST /api/alarms/{id}/comment - add a note to an alarm
+
POST /api/alarms/{id}/acknowledge - acknowledge an alarm
+
POST /api/alarms/{id}/process - process an alarm
+
POST /api/alarms/{id}/batch - batch update multiple alarms
+
POST /api/alarms/{id}/instructions - load alarm instructions
+ +
 
+ +
GET /api/events - list events
+
GET /api/events/updates - monitor for new events
+
GET /api/events/{id} - load individual item
+
GET /api/events/groups - list event groups
+
GET /api/events/groups/{id} - load individual event group
+
POST /api/events - create new event
+ +
 
+ +
GET /api/access_zones - search
+
GET /api/access_zones/{id} - load individual item
+
GET /api/access_zones/{id}/updates - monitor for status updates
+
GET /api/access_zones/update_cardholder_location - search for zones in which we can update cardholder locations
+
POST /api/access_zones/{id}/free - override to free access
+
POST /api/access_zones/{id}/free_pin - override to free-with-pin access
+
POST /api/access_zones/{id}/secure - override to secure access
+
POST /api/access_zones/{id}/secure_pin - override to secure-with-pin access
+
POST /api/access_zones/{id}/code_only - override to code only access
+
POST /api/access_zones/{id}/code_only_pin - override to code only-with-pin access
+
POST /api/access_zones/{id}/dual_auth - override to dual auth access
+
POST /api/access_zones/{id}/dual_auth_pin - override to dual auth-with-pin access
+
POST /api/access_zones/{id}/cancel - cancel any current override
+
POST /api/access_zones/{id}/lock_down - lockdown zone
+
POST /api/access_zones/{id}/cancel_lock_down - cancel lockdown
+
POST /api/access_zones/{id}/forgive_anti_passback - anti-passback forgive
+
POST /api/access_zones/{id}/set_zone_count - override cardholder count
+ +
 
+ +
GET /api/alarm_zones - search
+
GET /api/alarm_zones/{id} - load individual item
+
GET /api/alarm_zones/{id}/updates - monitor for status updates
+
POST /api/alarm_zones/{id}/arm - override to armed
+
POST /api/alarm_zones/{id}/disarm - override to disarmed
+
POST /api/alarm_zones/{id}/user1 - override to user1
+
POST /api/alarm_zones/{id}/user2 - override to user2
+
POST /api/alarm_zones/{id}/arm_high_voltage - override fence zones to armed-high-voltage
+
POST /api/alarm_zones/{id}/arm_low_feel - override fence zones to armed-low-feel
+
POST /api/alarm_zones/{id}/cancel - cancel override
+ +
 
+ +
GET /api/doors - search
+
GET /api/doors/{id} - load individual item
+
GET /api/doors/{id}/updates - monitor for status updates
+
POST /api/doors/{id}/open - override open the door
+ +
 
+ +
GET /api/elevator_groups - search
+
GET /api/elevator_groups/{id} - load individual item
+
GET /api/elevator_groups/modify_passenger_details - list of elevator groups in which we can modify passenger details
+ +
 
+ +
GET /api/fence_zones - search
+
GET /api/fence_zones/{id} - load individual item
+
GET /api/fence_zones/{id}/updates - monitor for status updates
+
GET /api/fence_zones/{id}/on - override to on
+
GET /api/fence_zones/{id}/off - override to off
+
GET /api/fence_zones/{id}/shunt - override to shunt
+
GET /api/fence_zones/{id}/unshunt - override to unshunt
+
GET /api/fence_zones/{id}/high_voltage - override to high_voltage
+
GET /api/fence_zones/{id}/low_feel - override to low_feel
+
GET /api/fence_zones/{id}/cancel - cancel override
+ +
 
+ +
GET /api/inputs - search
+
GET /api/inputs/{id} - load individual item
+
GET /api/inputs/{id}/updates - monitor for status updates
+
POST /api/inputs/{id}/shunt - override to shunt
+
POST /api/inputs/{id}/unshunt - override to unshunt
+
POST /api/inputs/{id}/isolate - override to isolate
+
POST /api/inputs/{id}/deisolate - override to deisolate
+ +
 
+ +
GET /api/outputs - search
+
GET /api/outputs/{id} - load individual item
+
GET /api/outputs/{id}/updates - monitor for status updates
+
POST /api/outputs/{id}/on - override to on
+
POST /api/outputs/{id}/off - override to off
+
POST /api/outputs/{id}/pulse - override to pulse
+
POST /api/outputs/{id}/cancel - cancel override
+ +
 
+ +
GET /api/macros - search
+
GET /api/macros/{id} - load individual item
+
GET /api/macros/{id}/updates - monitor for status updates
+
POST /api/macros/{id}/run - run macro
+ +
 
+ +
GET /api/readers - search
+
GET /api/readers/{id} - load individual item
+
GET /api/readers/{id}/updates - monitor for status updates
+
POST /api/readers/{id}/reset - override to reset
+
+ + + \ No newline at end of file diff --git a/ref/.gitignore b/ref/.gitignore new file mode 100644 index 0000000..4e8693a --- /dev/null +++ b/ref/.gitignore @@ -0,0 +1,9 @@ + +# Ony use minified Javascript and stylesheets. + +javascripts/* +!javascripts/*.min.js + +stylesheets/* +!stylesheets/*.min.css + diff --git a/ref/cardholders.html b/ref/cardholders.html new file mode 100644 index 0000000..76d9d3c --- /dev/null +++ b/ref/cardholders.html @@ -0,0 +1,16801 @@ + + + + + + Command Centre REST API: Cardholders | API Reference + + + + + + + +
+ +
+ +
+ +
+
+

Command Centre REST API: Cardholders + API Reference +

+
+
+
+

This document describes how you can use the Command Centre REST API to view and manage cardholders and their cards, mobile credentials, access groups, personal data, roles, competencies, personal data, and lockers.

+

It has companion documents describing the + PIV, + Alarms and Events, and + Access items APIs.

+

It assumes some familiarity with HTTP and REST interfaces. Before you start developing against this interface, you should also read the + Authentication section of the accompanying Alarms and Events API documentation to learn how to format your queries, and Command Centre's Configuration Client online help to learn how to ready the server to receive them (search for 'REST API').

+

Each section contains a list of use cases intended as quick solutions for simple tasks or a how-to to get you started toward your particular goal. However they also serve as a good introduction to the API if you step through them using a web browser, starting at /api on your Command Centre server. To do that, you will need a browser plugin that lets you set an Authorization HTTP header, and a JSON formatter to prettify the results documents. Search the Configuration client's online help for 'test REST API' for guidance. You will also need to prepare Command Centre, as above.

+

Licensing

+

All of the API calls described here except visitor management are available with the RESTCardholders licence. Visitor management calls need both RESTCardholders and VisitorManagement. Without RESTCardholders, lockers and locker banks are available with the RESTStatus licence, and partially 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.

+

Cardholder API changes on the roadmap

+
    +
  • +

    You will be able to create, delete, and change the the basic configuration of access groups, competencies, PDFs, and card types.

    +
  • +
  • +

    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, but since it was not documented, none should.

    +
  • +
  • +

    POTENTIALLY BREAKING CHANGE: PATCH methods may return "200 Success" instead of "204 No Content" and will contain 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's visits and escort.

    +
  • +
  • +

    "Revealable" access levels on PDFs.

    +
  • +
  • +

    POTENTIALLY BREAKING CHANGE: a future version will sort its search results differently if your query does not include a parameter to override the default.

    +
  • +
  • +

    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.

    +
  • +
  • +

    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.

    +
  • +
+

Cardholder API changes in 9.00

+
    +
  • +

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

    +
  • +
  • +

    GovPass cards have two new fields, visitorContractor and ownedBySite.

    +
  • +
+

Cardholder API changes in 8.90

+
    +
  • +

    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.

    +
  • +
+

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

    +
  • +
+

Cardholder API changes in 8.70

+
    +
  • +

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

    +
  • +
+

Cardholder API changes in 8.60

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

API changes in 8.50

+
    +
  • +

    The server property that turns off + client certificate checking changed.

    +
  • +
  • +

    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.

    +
  • +
+

API changes in 8.40

+
    +
  • +

    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

+
    +
  • +

    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.

    +
  • +
+

API changes in 8.20

+
    +
  • +

    A new field update_location on a cardholder gives a link to a + POST that changes his or her current location (access zone).

    +
  • +
  • +

    Lockers and locker banks are now visible with the RESTStatus licence, including whether a locker is available or assigned. 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

+

All changes contain the text "8.10".

+
    +
  • +

    A + card types API that returns the card types your operator is able to use when assigning cards to cardholders.

    +
  • +
  • +

    A + PDF API that returns the PDF 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.

    +
  • +
+

Efficiency tips

+
    +
  • +

    When searching for a cardholder (or any other object) by name or PDF value, if you have the exact string, put it in quotes "...". It is still a case-insensitive search but it attempts to match the entire string. Without the quotes Command Centre will perform a substring search, which is slower and may return more results than you need. In one test, a substring search of half a million cardholders took more than ten seconds, but with quotes it took a tenth of a second. That is a 100x speedup. If the server believes you have omitted the quotes in error, it will include a polite reminder in its results.

    +
  • +
  • +

    When downloading a significant number of items, sort by ID.

    +
  • +
  • +

    Set your page size as large as your client can handle. The default was 100 before 8.70; using the top parameter to take it to 1000 or more gives + much better throughput.

    +
  • +
+

To give you an idea of the effect of setting sort and top, here are some timings for a download of 200,000 cardholders from CC 8.40:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SortingPaginationTime
Default (by name)Default (100)28 minutes
sort=idDefault (100)5 minutes
Default (by name)top=10003.5 minutes
sort=idtop=10001 minute
+
    +
  • +

    In version 8.00 or later, use the + fields parameter to add fields to the results of a search, saving you GETting the details page.

    +

    For example, to see the names, PDFs, and access groups of all your cardholders:

    +

    GET /api/cardholders?sort=id&top=10000 &fields=firstname,lastname,personalDataFields,accessGroups

    +

    It adds to the payload size, though, so only do this if you are certain that the search will not return too many cardholders you are not interested in.

    +
  • +
  • +

    If you are writing an integration that synchronises users from a source of truth to Command Centre, see the 'Synchronising a user directory' use case in the Cardholders section.

    +
  • +
  • +

    If you must get an object's details page, and you are on v8.00 or later, using the + fields parameter will still save work. Be explicit with what you want: do not use fields=defaults unless you need every field that 'defaults' gives you and you accept that we may add fields to the defaults in future.

    +
  • +
  • +

    When creating cardholders, assign their cards, groups, and other attributes in the POST. This is significantly quicker than sending a handful of PATCHes chasing after your POST, and if you are interrupted, you will not end up with a partially-configured cardholder.

    +
  • +
  • +

    When modifying a cardholder, do all your operations in one PATCH. This is much quicker than doing it in several smaller PATCHes.

    +
  • +
+

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.

+

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.

+
+
+
+
Request Content-Types: + application/json +
+
Response Content-Types: + application/json +
+
Schemes: + https +
+
Version: + 1.0.0 +
+
+
+
+
+ +

Authentication

+
+
+
+

+ API key + +

+
+

Clients authenticate by including a pre-shared API key in the Authorization header of each request. Command Centre generates an 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.

+

The API key will be in the format XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX and should be in the header following an authorisation method of GGL-API-KEY and a space. Both should be in upper case. For example:

+

Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB

+

Early versions of Command Centre allowed you to omit the 'GGL-API-KEY'. Later versions of CC and all versions of the API gateway will reject you with a 401 if you do that.

+

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.

+

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.

+

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.

+
+
+
+
+
type
+
+
apiKey
+
+
+
+
name
+
+
Authorization
+
+
+
+
in
+
+
header
+
+
+
+
x-external
+
+
eventsApi.yaml#/securityDefinitions/API key
+
+
+
+
+
+

Documentation suite

+
+
+
+

The API's reference documentation divides into:

+ + + + + + + + + + + + +

+ + + + + +
+ Alarms, events, and non-cardholder items + +

The alarms and events APIs let you download, monitor, and create events, and download, monitor, and manage alarms.

+

The alarms and events documentation also covers API calls that support divisions and items as they relate to events, as well as bulk status monitoring.

+
+ Cardholders and related items + The cardholder parts of the API let you manage your users, their personal data and credentials (cards), and their links to associated items such as access groups, roles, operator groups, competencies, and lockers.
+ Status and overrides + +

These functions let you monitor and override the types of Command Centre items that have their own status, including access zones, alarm zones, doors, fence zones, inputs, outputs, macros, elevator groups, interlock groups, and schedules.

+

Despite its name this section does not cover + mass-monitoring item status.

+ PIV cards + This supplement describes how to work with PIV and PIV-I cards. It is separate from the main cardholder documentation in the interest of brevity.
+
+
+
+

Forward compatibility (HATEOAS)

+
+
+
+

This is a self-referencing REST API that follows the principles of HATEOAS. Other than the initial GET to /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 licensed API calls.

+

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.

+
+
+
+

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.

    +
  • +
+
+
+
+

Field specifiers

+
+
+
+

You can specify the fields you want in the results of a query. This lets you:

+
    +
  • save calls by adding more fields to search results, which are terse by default
  • +
  • save bandwidth by putting fewer fields on detail pages, which are verbose by default, and
  • +
  • retrieve more data by adding fields to detail pages that are not normally there, such as an item's status, short name, and notes.
  • +
+

You do this using the query parameter fields to the search, detail, and updates pages. It takes a comma-separated list of field names. They are case-sensitive: copy them carefully from this document. The special field name defaults adds in the fields the server would have sent had you not set the fields parameter at all.

+

Example GETs:

+
    +
  • +

    /api/cardholders?name=Smith&fields=defaults,cards,competencies will list all cardholders with 'Smith' in the first or last name, showing the fields you normally get on a search page plus their cards and competencies.

    +
  • +
  • +

    /api/access_groups/123?fields=children,cardholders will show just the child groups of access group 123 and a link to retrieve its cardholders - nothing else.

    +
  • +
  • +

    /api/outputs?fields=name,shortname,id will show the name, short name, and ID of your outputs.

    +
  • +
  • +

    /api/access_zones?fields=name,doors will show the name and attached doors of your access zones.

    +
  • +
  • +

    /api/alarms?fields=time,message,details will return the time, message text, and details text of your alarms. Not only does that save a lot of data by removing default fields, but it adds the details field which does not normally come out of an alarm search.

    +
  • +
+

Each controller has different optional fields - see their sections under Operations.

+
+
+
+

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 won't 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.

+

Use cases

+

Searching for cardholders by name

+
    +
  1. GET /api.
  2. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Downloading all cardholders

+
    +
  1. +

    GET /api

    +
  2. +
  3. +

    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!

    +
  4. +
  5. +

    Process the bundle of cardholders in the result.

    +
  6. +
  7. +

    Follow the link at next.href if there is one, and repeat.

    +
  8. +
+

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. +
  3. Do a one-off sync of all cardholders.
  4. +
  5. Loop to stay up to date, starting with the bookmark.
  6. +
+

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

    v8.00 or earlier: 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. +
  5. +

    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 should only be one since PDFs must have different names).

    +
  6. +
  7. +

    Note the ID of the item. It will be a short alphanumeric.

    +
  8. +
+

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', assuming you had 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. +
  3. Follow the link at features.cardholders.cardholders.href + , adding a query separator and pdf_<your_pdf_id>=<your_pdf_value>, without the angle brackets.
  4. +
+

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

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

    +
  4. +
  5. +

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

    +
  6. +
  7. +

    Compose a JSON body using + this example and + this detail, then + POST it to the href at features.cardholders.cardholders.href + on the /api page.

    +
  8. +
+

Modifying a cardholder

+
    +
  1. +

    Find your cardholder using one of the processes above.

    +
  2. +
  3. +

    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.

    +
  4. +
  5. +

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

    +
  6. +
+

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

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

    +
  4. +
  5. +

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

    +
  6. +
+

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. +
  3. Take the array at accessGroups, rename it to remove, and put it in object called accessGroups at the root level of a + JSON object.
  4. +
  5. + PATCH the cardholder with that JSON object. That will remove the cardholder from all the groups that came back in the GET.
  6. +
+

Assigning a new card to a cardholder

+
    +
  1. GET /api.
  2. +
  3. Follow the link at features.cardTypes.cardTypes.href + to find the href for the new card's type.
  4. +
  5. If you want to create a card in a state other than the default, get the state from the same page.
  6. +
  7. Find the href for the cardholder using one of the search methods above.
  8. +
  9. 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.
  10. +
  11. PATCH the cardholder's href according to the + cardholder patch schema.
  12. +
+

Deleting a cardholder's PDF value

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

Deleting a cardholder

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

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. +
  3. 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.
  4. +
  5. Send an + HTTP POST to that cardholder's updateLocation.href link.
  6. +
+

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

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

Finding recently-created cardholders

+

This is another function of the + Cardholder changes call, described there.

+

However to do that you need to make an API call + before creating the cardholders. If the cardholders 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.

+
+
+
+ + +
+ Cardholders + +
+ + +

+ Search cardholders +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.cardholders.cardholders section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

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.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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}
+
in query
+
+ string + +
+
+
+

Limits the results to cardholders with a value for the Personal Data Field with this ID that matches the parameter.

+

By default, it is a substring match; 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, adding a percent sign % or underscore _ will anchor the string at both ends. A _ will match any single character, and a % will match any substring. A lone % will return any cardholder with this PDF set to a non-null value.

+

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.

+

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

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
accessZone
+
in query
+
+ string[] + +
+
+
+

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, or if an operator manually moves them outside.

+
+
+
+
+
fields
+
in query
+
+ string[] + + defaults +
+
+
+

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

+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
400 Bad Request
+
+
+

The server could not make sense of your search terms.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325",
+      "id": "325",
+      "firstName": "Algernon",
+      "lastName": "Boothroyd",
+      "shortName": "Q",
+      "description": "Quartermaster",
+      "authorised": true
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/cardholders?skip=61320"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Create a cardholder +

+
+
+
+ POST + /api/cardholders +
+
+
+
+
+
+

Creates a new cardholder, including his or her cards, group memberships, personal data, competencies, and roles.

+

Take this URL 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.

+
+
+
+
+
+
+
+ +
+ +

This can be a large object as shown, or a tiny one, because the only fields you must have in the POST are the division and either the first or last name.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "firstName": "Algernon",
+  "lastName": "Boothroyd",
+  "shortName": "Q",
+  "description": "Quartermaster",
+  "authorised": true,
+  "division": {
+    "href": "https://localhost:8904/api/divisions/5387"
+  },
+  "@email": "user@sample.com",
+  "@headshot": "/9j/4A...==",
+  "personalDataDefinitions": [
+    {
+      "@email": {
+        "notifications": true
+      }
+    }
+  ],
+  "cards": [
+    {
+      "type": {
+        "href": "https://localhost:8904/api/card_types/600"
+      },
+      "pin": "153624"
+    },
+    {
+      "type": {
+        "href": "https://localhost:8904/api/card_types/654"
+      },
+      "number": "Nick's mobile",
+      "invitation": {
+        "email": "nick@example.com",
+        "mobile": "02123456789",
+        "singleFactorOnly": true
+      }
+    }
+  ],
+  "accessGroups": [
+    {
+      "accessgroup": {
+        "href": "https://localhost:8904/api/access_groups/352"
+      },
+      "from": "2019-01-01"
+    }
+  ],
+  "operatorGroups": [
+    {
+      "operatorgroup": {
+        "href": "https://localhost:8904/api/operator_groups/523"
+      }
+    }
+  ],
+  "competencies": [
+    {
+      "competency": {
+        "href": "https://localhost:8904/api/competencies/2354"
+      },
+      "enabled": false,
+      "enablement": "2019-01-01"
+    }
+  ],
+  "notes": "",
+  "notifications": {
+    "enabled": true,
+    "from": "2017-10-10T14:59:00Z",
+    "until": "2017-10-17T14:59:00Z"
+  },
+  "relationships": [
+    {
+      "role": {
+        "href": "https://localhost:8904/api/roles/5396"
+      },
+      "cardholder": {
+        "href": "https://localhost:8904/api/cardholders/5398"
+      }
+    }
+  ],
+  "lockers": [
+    {
+      "locker": {
+        "href": "https://localhost:8904/api/lockers/3456"
+      }
+    },
+    {
+      "locker": {
+        "href": "https://localhost:8904/api/lockers/3457"
+      }
+    }
+  ],
+  "elevatorGroups": [
+    {
+      "elevatorGroup": {
+        "href": "https://localhost:8904/api/elevator_groups/635"
+      },
+      "accessZone": {
+        "href": "https://localhost:8904/api/access_zones/637"
+      }
+    },
+    {
+      "elevatorGroup": {
+        "href": "https://localhost:8904/api/elevator_groups/639"
+      },
+      "enableCodeBlueFeatures": true
+    }
+  ]
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new cardholder.

+
+ string + (url) + +
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Get details of a cardholder +

+
+
+
+ GET + /api/cardholders/{id} +
+
+
+
+
+
+

Full details for a cardholder. Follow the href in the + cardholder search to get here.

+
+
+
+
+
+
+
+
+
fields
+
in query
+
+ string[] + + defaults +
+
+
+

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.

+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the cardholder.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
404 Not Found
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325",
+  "id": "325",
+  "firstName": "Algernon",
+  "lastName": "Boothroyd",
+  "shortName": "Q",
+  "description": "Quartermaster",
+  "authorised": true,
+  "lastSuccessfulAccessTime": "2004-11-18T19:21:52Z",
+  "lastSuccessfulAccessZone": {
+    "href": "https://localhost:8904/api/access_zones/333",
+    "name": "Twilight zone"
+  },
+  "serverDisplayName": "ruatoria.satellite.int",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "@Student ID": "8904640",
+  "disableCipherPad": false,
+  "usercode": "numeric, and write-only",
+  "operatorLoginEnabled": true,
+  "operatorUsername": "qmaster",
+  "operatorPassword": "write-only",
+  "operatorPasswordExpired": false,
+  "windowsLoginEnabled": true,
+  "windowsUsername": "misix.local\\qmaster",
+  "personalDataDefinitions": [
+    {
+      "@Student ID": {
+        "href": "https://localhost:8904/api/cardholders/325/personal_data/2356",
+        "definition": {
+          "href": "https://localhost:8904/api/personal_data_fields/2356",
+          "name": "Student ID",
+          "id": "2356",
+          "type": "string"
+        },
+        "value": "8904640"
+      }
+    },
+    {
+      "@Photo": {
+        "href": "https://localhost:8904/api/cardholders/325/personal_data/2369",
+        "definition": {
+          "href": "https://localhost:8904/api/personal_data_fields/2369",
+          "name": "Photo",
+          "id": "2369",
+          "type": "image"
+        },
+        "value": {
+          "href": "https://localhost:8904/api/cardholders/325/personal_data/2369"
+        }
+      }
+    }
+  ],
+  "cards": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9",
+      "number": "1",
+      "cardSerialNumber": "045A5769713E80",
+      "issueLevel": 1,
+      "status": {
+        "value": "Disabled (manually)",
+        "type": "inactive"
+      },
+      "type": {
+        "href": "https://localhost:8904/api/card_types/354",
+        "name": "Card type no. 1"
+      },
+      "invitation": {
+        "email": "nick@example.com",
+        "mobile": "02123456789",
+        "singleFactorOnly": true,
+        "status": "sent",
+        "href": "https://security.gallagher.cloud/api/invitations/abcd1234defg5678"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2017-12-31T11:59:59Z",
+      "credentialClass": "mobile",
+      "trace": false,
+      "lastPrintedOrEncodedTime": "2020-08-10T09:20:50Z",
+      "lastPrintedOrEncodedIssueLevel": 1,
+      "pin": "153624",
+      "visitorContractor": false,
+      "ownedBySite": false,
+      "credentialId": "reserved",
+      "bleFacilityId": "reserved"
+    }
+  ],
+  "accessGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/access_groups/D714D8A89F",
+      "accessGroup": {
+        "name": "R&D special projects group",
+        "href": "https://localhost:8904/api/access_groups/352"
+      },
+      "status": {
+        "value": "Pending",
+        "type": "pending"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2017-12-31T11:59:59Z"
+    }
+  ],
+  "operatorGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD",
+      "operatorGroup": {
+        "name": "Locker admins",
+        "href": "https://localhost:8904/api/operator_groups/532"
+      }
+    }
+  ],
+  "competencies": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/competencies/2dc3p0",
+      "competency": {
+        "href": "https://localhost:8904/api/competencies/2354",
+        "name": "Hazardous goods handling"
+      },
+      "status": {
+        "value": "Pending",
+        "type": "pending"
+      },
+      "expiryWarning": "2017-03-06T15:45:00Z",
+      "expiry": "2017-03-09T15:45:00Z",
+      "enablement": "2018-03-09T15:45:00Z",
+      "comment": "CPR refresher due March.",
+      "limitedCredit": true,
+      "credit": 37
+    }
+  ],
+  "edit": {
+    "href": "https://localhost:8904/cardholders/325/edit"
+  },
+  "updateLocation": {
+    "href": "https://localhost:8904/api/cardholders/402/update_location"
+  },
+  "notes": "",
+  "notifications": {
+    "enabled": true,
+    "from": "2017-10-10T14:59:00Z",
+    "until": "2017-10-17T14:59:00Z"
+  },
+  "relationships": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/relationships/179lah1170",
+      "role": {
+        "href": "https://localhost:8904/api/roles/5396",
+        "name": "Supervisor"
+      },
+      "cardholder": {
+        "href": "https://localhost:8904/api/cardholders/5398",
+        "name": "Miles Messervy",
+        "firstName": "Miles",
+        "lastName": "Messervy"
+      }
+    }
+  ],
+  "lockers": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/lockers/t1m4",
+      "locker": {
+        "name": "Bank A locker 1",
+        "shortName": "A1",
+        "lockerBank": {
+          "href": "https://localhost:8904/api/locker_banks/4567",
+          "name": "Bank A"
+        },
+        "href": "https://localhost:8904/api/lockers/3456"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2018-12-31T00:00:00Z"
+    }
+  ],
+  "elevatorGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/elevator_groups/567",
+      "elevatorGroup": {
+        "href": "https://localhost:8904/api/elevator_groups/635",
+        "name": "Main building lower floors"
+      },
+      "accessZone": {
+        "href": "https://localhost: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": "object",
+  "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"
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Update a cardholder +

+
+
+
+ PATCH + /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.

+
+

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 Cardholder to disable cards.

    +
  • +
  • +

    Modify Access Control. This privilege does not work on its own. You need Edit Cardholder as well as Modify Access Control to change group memberships.

    +
  • +
  • +

    Add or Edit Cardholder Notes. You also need Edit Cardholder to change notes on an existing cardholder via the API. In the administrative clients, you do not.

    +
  • +
  • +

    Manage Locker Assignments. You also need Edit Cardholder 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 Cardholder privilege.

+

In short, if your application intends to do more to cardholders than de-authorise them, you will need an operator with Edit Cardholder.

+
+

The PATCH is best illustrated by example.

+
+
+
+
+
+
+
+ +
+ +

As well as cardholder attributes such as 'authorised', the PATCH body contains instructions for creating, updating, and deleting personal data, group memberships, etc.

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the cardholder.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "authorised": true,
+  "@employeeId": "THX1139",
+  "personalDataDefinitions": [
+    {
+      "@email": {
+        "notifications": true
+      }
+    },
+    {
+      "@cellphone": {
+        "notifications": false
+      }
+    }
+  ],
+  "cards": {
+    "add": [
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/354"
+        },
+        "pin": "153624"
+      },
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/600"
+        },
+        "number": "Jock's iPhone 8",
+        "status": {
+          "value": "Pending sign-off"
+        },
+        "invitation": {
+          "email": "jock@example.com"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
+        "issueLevel": 2,
+        "until": "",
+        "status": {
+          "value": "Stolen"
+        },
+        "pin": "153624"
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
+        "status": {
+          "value": "Lost"
+        }
+      }
+    ]
+  },
+  "accessGroups": {
+    "add": [
+      {
+        "accessGroup": {
+          "href": "https://localhost:8904/api/access_groups/352"
+        }
+      },
+      {
+        "accessGroup": {
+          "href": "https://localhost:8904/api/access_groups/124"
+        },
+        "until": "2019-12-31"
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/access_groups/10ad21",
+        "until": ""
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/access_groups/10ed27"
+      }
+    ]
+  },
+  "competencies": {
+    "add": [
+      {
+        "competency": {
+          "href": "https://localhost:8904/api/competencies/2354"
+        },
+        "enabled": false,
+        "enablement": "2021-01-01T08:00+13"
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/competencies/2dc3",
+        "enabled": true
+      }
+    ]
+  },
+  "relationships": {
+    "add": [
+      {
+        "role": {
+          "href": "https://localhost:8904/api/roles/5396"
+        },
+        "cardholder": {
+          "href": "https://localhost:8904/api/cardholders/5398"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/roles/1799lah1170",
+        "cardholder": {
+          "href": "https://localhost:8904/api/cardholders/10135"
+        }
+      }
+    ]
+  },
+  "lockers": {
+    "add": [
+      {
+        "locker": {
+          "href": "https://localhost:8904/api/lockers/1200",
+          "from": "2019-01-01"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/lockers/wxyz1234",
+        "until": "2020-02-29"
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/lockers/abcd4321"
+      }
+    ]
+  },
+  "operatorGroups": {
+    "add": [
+      {
+        "operatorGroup": {
+          "href": "https://localhost:8904/api/operator_groups/532"
+        }
+      },
+      {
+        "operatorGroup": {
+          "href": "https://localhost:8904/api/operator_groups/535"
+        }
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD"
+      }
+    ]
+  },
+  "elevatorGroups": {
+    "add": [
+      {
+        "elevatorGroup": {
+          "href": "https://localhost:8904/api/elevator_groups/635"
+        },
+        "accessZone": {
+          "href": "https://localhost:8904/api/access_zones/637"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/elevator_groups/1268613268",
+        "enableCodeBlueFeatures": true,
+        "enableVipFeatures": false
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/elevator_groups/3498734"
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

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

+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
409 Conflict
+
+
+

The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
4xx
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove a cardholder +

+
+
+
+ DELETE + /api/cardholders/{id} +
+
+
+
+
+
+

This call removes a cardholder from Command Centre.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the cardholder.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

There is no such cardholder, or the operator does not have permissions to delete it, or the server is not licensed for cardholder operations.

+
+
+
+
+
409 Conflict
+
+
+

The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove an access group membership +

+
+
+
+ DELETE + /api/cardholders/{id}/access_groups/{membership_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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder.

+
+
+
+
+
membership_id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder's membership in the access group.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

That is not the href of an access group membership.

+
+
+
+
+
409 Conflict
+
+
+

The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
4xx
+
+
+

The operator does not have a privilege that allows editing that cardholder's access group memberships (such as 'Modify access control').

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove a card from a cardholder +

+
+
+
+ DELETE + /api/cardholders/{id}/cards/{card_id} +
+
+
+
+
+
+

This call removes a card from a cardholder.

+

You can find this URL in the + cardholder object.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder.

+
+
+
+
+
card_id
+ +
in path
+
+ string + +
+
+
+

An opaque identifier for the card.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

That card is not on that cardholder.

+
+
+
+
+
409 Conflict
+
+
+

The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
+
+
+ +
+ + +
+ Cardholders + +
+ + +

+ Change competency credit +

+
+
+
+ POST + /api/cardholders/{id}/competencies/{link_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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder.

+
+
+
+
+
link_id
+ +
in path
+
+ string + +
+
+
+

An opaque identifier for the link between the cardholder and the competency.

+
+
+
+
+
credit
+ +
in path
+
+ integer + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

The parameters are invalid.

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove an elevator group from a cardholder +

+
+
+
+ DELETE + /api/cardholders/{id}/elevator_groups/{assignment_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.

+
+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

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

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove a locker assignment +

+
+
+
+ DELETE + /api/cardholders/{id}/lockers/{assignment_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.

+
+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
4xx
+
+
+

You do not have privileges for the operation.

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove an operator group membership +

+
+
+
+ DELETE + /api/cardholders/{id}/operator_groups/{membership_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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder.

+
+
+
+
+
membership_id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder's membership in the operator group.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

That is not the href of an operator group membership.

+
+
+
+
+
409 Conflict
+
+
+

The cardholder is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Remove a relationship +

+
+
+
+ DELETE + /api/cardholders/{id}/roles/{relationship_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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the cardholder.

+
+
+
+
+
relationship_id
+ +
in path
+
+ string + +
+
+
+

An opaque identifier for the link between the cardholder and the role.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Change a cardholder's location +

+
+
+
+ POST + /api/cardholders/{id}/update_location +
+
+
+
+
+
+

This call updates a cardholder's location (moves them) to a target access zone. Added in 8.20.

+

Take this URL 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.

+

Note that to change a cardholder's location 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.

+

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.

+
+
+
+
+
+
+
+ +
+ +

This should contain the href of the target access zone. The example here is a different style from the linked example, which contains access_zones in the URL rather than items, because it came from the items controller. Both styles work.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "accessZone": {
+    "href": "https://localhost:8904/items/412"
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

The cardholder ID or access zone href is invalid.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Cardholders + +
+ + +

+ Find out which fields you can edit +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+
+

Success.

+
+
+
+
+
4xx
+
+
+

The parameter is invalid or you do not have privileges for the operation.

+
+
+
+
+
+
+
+

Cardholder changes

+
+
+

This feature keeps you up to date with cardholder changes. It works for changes 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 ask for all the changes that happen after this point.

    +
  2. +
  3. +

    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.

    +
  4. +
  5. +

    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.

    +
  6. +
  7. +

    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.

    +
  8. +
  9. +

    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.

    +
  10. +
  11. +

    Restart your polling loop using the link in next.href.

    +
  12. +
+

Use case: discovering recently-created cardholders.

+

The first thing you need to do is get a link from the changes API call + before creating the cardholders. 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 by removing everything else with fields=newValues.

+

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 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 + /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, don't 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.

+

Take this URL 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.

    +
  • +
+
+
+
+
+
+
+
+
+
top
+
in query
+
+ integer + 1 ≤ x ≤ 1000 +
+
+
+

Sets the maximum number of changes to return per page.

+
+
+
+
+
pos
+
in query
+
+ integer + x ≥ 0 + +
+
+
+

Reserved for internal use.

+
+
+
+
+
filter
+
in query
+
+ string[] + + href, + id, + firstName, + lastName, + shortName, + description, + authorised, + lastSuccessfulAccessZone, + division, + notes, + useExtendedAccessTime, + personalDataFields, + operatorLoginEnabled, + operatorUsername, + operatorPassword, + operatorPasswordExpired, + windowsLoginEnabled, + windowsUsername, + cards, + accessGroups, + competencies, + notifications, + relationships, + lockers + + + defaults +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + operator, + operator.href, + operator.name, + time, + type, + item, + oldValues, + newValues, + cardholder, + cardholder.* + + + defaults +
+
+
+

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 to monitor changes to PDF values on a cardholder.

+
+
+
+
+
deadline
+
in query
+
+ integer + x ≥ 0 + 50 +
+
+
+

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.

+

Added in 8.80.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
500 Internal Server Error
+
+
+

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

+
+
+
+
+
4xx
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/cardholders/changes/f4e67a",
+      "time": "2020-01-14T03:14:33Z",
+      "type": "update",
+      "item": {
+        "href": "https://localhost:8904/api/cardholders/525"
+      },
+      "operator": {
+        "name": "System Operator",
+        "href": "https://localhost:8904/api/items/1"
+      },
+      "oldValues": {
+        "firstName": "Craig",
+        "competencies": [
+          {
+            "enablement": "",
+            "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4"
+          }
+        ]
+      },
+      "newValues": {
+        "firstName": "Gavin",
+        "competencies": [
+          {
+            "enablement": "2020-02-29T00:00:00Z",
+            "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4"
+          }
+        ],
+        "cards": [
+          {
+            "number": "2",
+            "cardSerialNumber": "",
+            "issueLevel": 1,
+            "from": "",
+            "until": "",
+            "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba"
+          }
+        ],
+        "accessGroups": [
+          {
+            "accessGroup": {
+              "name": "Access Group 1",
+              "href": "https://localhost:8904/api/access_groups/499"
+            },
+            "from": "",
+            "until": "2020-01-15T04:39:00Z",
+            "href": "https://localhost:8904/api/cardholders/525/access_groups/f9cb328b4"
+          }
+        ]
+      },
+      "cardholder": {
+        "firstName": "Gavin",
+        "cards": [
+          {
+            "number": "2",
+            "cardSerialNumber": "",
+            "issueLevel": 1,
+            "from": "",
+            "until": "",
+            "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba"
+          }
+        ]
+      }
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/cardholders/changes?pos=SWEp9"
+  }
+}
+
+ + +
+
+
+
+

Access groups

+
+
+

These methods give you read access to the access groups in Command Centre. You can search for groups, see their lineage, list their cardholder members, and (in 8.40 or later) see how they affect those members.

+

Your REST operator will need the 'View' or 'Edit Access Groups' operator privileges for the GETs.

+

They 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 Cardholder' privilege on the cardholder for that.

+

Use cases

+

Finding members of an access group, and managing memberships.

+
    +
  1. +

    GET /api.

    +
  2. +
  3. +

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

    +
  4. +
  5. +

    Find your access group in the results.

    +
  6. +
  7. +

    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.

    +
  8. +
  9. +

    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.

    +
  10. +
+

Building the group hierarchy.

+
    +
  1. GET /api.
  2. +
  3. 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.
  4. +
  5. 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.
  6. +
  7. Follow the link at next.href, if there is one, and repeat.
  8. +
+

You can now assemble the groups into a tree.

+
+
+
+ + + + + +

+ Search access groups +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

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.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + description, + parent, + division, + children, + notes, + personalDataDefinitions, + cardholders, + access, + saltoAccess, + alarmZones, + ... + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "id": "352",
+      "href": "https://localhost:8904/api/access_groups/352",
+      "name": "R&D special projects group.",
+      "description": "Deep underground.",
+      "parent": {
+        "href": "https://localhost:8904/api/access_groups/100",
+        "name": "All R&D"
+      },
+      "division": {
+        "id": "2",
+        "href": "https://localhost:8904/api/divisions/2"
+      },
+      "cardholders": {
+        "href": "https://localhost:8904/api/access_groups/352/cardholders"
+      },
+      "serverDisplayName": "ruatoria.satellite.int"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/access_groups?skip=61320"
+  }
+}
+
+ + +
+
+
+
+
+ + + + + +

+ Get details of an access group +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the access group.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + description, + parent, + division, + children, + notes, + personalDataDefinitions, + access, + saltoAccess, + alarmZones, + ... + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
404 Not 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'.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "id": "352",
+  "href": "https://localhost:8904/api/access_groups/352",
+  "name": "R&D special projects group.",
+  "description": "Deep underground.",
+  "parent": {
+    "href": "https://localhost:8904/api/access_groups/100",
+    "name": "All R&D"
+  },
+  "division": {
+    "id": "2",
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "cardholders": {
+    "href": "https://localhost:8904/api/access_groups/352/cardholders"
+  },
+  "serverDisplayName": "ruatoria.satellite.int",
+  "children": [
+    {
+      "href": "https://localhost:8904/api/access_groups/5122",
+      "name": "R&D super-special projects"
+    },
+    {
+      "href": "https://localhost:8904/api/access_groups/3420",
+      "name": "R&D social committee"
+    }
+  ],
+  "personalDataDefinitions": [
+    {
+      "name": "email",
+      "href": "https://localhost:8904/api/personal_data_fields/5516"
+    },
+    {
+      "name": "cellphone",
+      "href": "https://localhost:8904/api/personal_data_fields/9998"
+    }
+  ],
+  "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://localhost:8904/api/access_zones/333",
+        "name": "Twilight zone"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/5",
+        "name": "Default Cardholder Access Granted"
+      }
+    },
+    {
+      "accessZone": {
+        "href": "https://localhost:8904/api/access_zones/412",
+        "name": "Server room"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/557",
+        "name": "8am-5pm weekdays"
+      }
+    }
+  ],
+  "saltoAccess": [
+    {
+      "saltoItemType": {
+        "value": "saltoAccessZone"
+      },
+      "saltoItem": {
+        "href": "https://localhost:8904/api/items/570",
+        "name": "Salto BLE CV19"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/5",
+        "name": "Default Cardholder Access Granted"
+      }
+    },
+    {
+      "saltoItemType": {
+        "value": "saltoDoor"
+      },
+      "saltoItem": {
+        "href": "https://localhost:8904/api/items/579",
+        "name": "Salto CU5000"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/557",
+        "name": "8am-5pm weekdays"
+      }
+    }
+  ],
+  "alarmZones": [
+    {
+      "alarmZone": {
+        "href": "https://localhost:8904/api/alarm_zones/328",
+        "name": "Roswell building 2 lobby alarms"
+      }
+    },
+    {
+      "alarmZone": {
+        "href": "https://localhost:8904/api/alarm_zones/10138",
+        "name": "Roswell building 3 lobby alarms"
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+
+ + + + + +

+ Get membership of an access group +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the access group.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
type
+
+ + + Access group membership + + + +
+
+
+
+
404 Not 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.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "cardholders": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/access_groups/D714D8A894724F",
+      "cardholder": {
+        "name": "Boothroyd, Algernon",
+        "href": "https://localhost:8904/api/cardholders/325"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2017-12-31T11:59:59Z"
+    },
+    {
+      "href": "https://localhost:8904/api/cardholders/329/access_groups/18DE901A1DEA48",
+      "cardholder": {
+        "name": "Miles Messervy"
+      },
+      "from": "2016-11-18T00:00:00Z"
+    }
+  ]
+}
+
+ + +
+
+
+
+

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.

+
+
+
+ + +
+ Competencies + +
+ + +

+ Create a competency [coming soon] +

+
+
+
+ POST + /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.

+

Creating a competency is coming in a future version of Command Centre.

+
+
+
+
+
+
+
+ +
+ +

There are no mandatory fields. If you do not specify a division when creating a competency, or any other item for that matter, the API will put it in the root division.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "New competency",
+  "shortName": "C4",
+  "description": "Translated automatically.",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "A very long string."
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

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

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new competency.

+
+ string + (url) + +
+
+
+
+
+
+
+ + +
+ Competencies + +
+ + +

+ Search competencies +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + shortName, + description, + division, + notes, + expiryNotify, + noticePeriod, + defaultExpiry, + defaultAccess + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

The installation lacks a cardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Competencies + +
+ + +

+ Get details of a competency +

+
+
+
+ GET + /api/competencies/{id} +
+
+
+
+
+
+

You get this URL from a cardholder or from a competency search.

+

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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The alphanumeric identifier of the competency you are after.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + shortName, + description, + division, + notes, + expiryNotify, + noticePeriod, + defaultExpiry, + defaultAccess + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The installation lacks a cardholders licence.

+
+
+
+
+
404 Not Found
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "href": "https://localhost:8904/api/competencies/2354",
+  "id": "2354",
+  "name": "Hazardous goods handling",
+  "description": "Required for access to chem sheds.",
+  "serverDisplayName": "ruatoria.satellite.net",
+  "division": {
+    "id": "2",
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "",
+  "shortName": "",
+  "expiryNotify": false,
+  "noticePeriod": {
+    "units": "weeks",
+    "number": 2
+  },
+  "defaultExpiry": {
+    "expiryType": "durationmonths",
+    "expiryValue": 6
+  },
+  "defaultAccess": "fullAccess"
+}
+
+ + +
+
+
+
+
+ + +
+ Competencies + +
+ + +

+ Update a competency [coming soon] +

+
+
+
+ PATCH + /api/competencies/{id} +
+
+
+
+
+
+

This is the call you use to update a division's name, short name, description, notes, or division.

+

The PATCH expects a document in the same format as the + the competency detail but with fewer fields. An example is + this PATCH example.

+

Patching competencies is coming to a future version of Command Centre.

+
+
+
+
+
+
+
+ +
+ +

There are no mandatory fields.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "New competency",
+  "shortName": "C4",
+  "description": "Translated automatically.",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "A very long string."
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

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

+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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 either the 'Configure Site' 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.

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

+
+
+
+
+
409 Conflict
+
+
+

The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
+
+
+
+ + +
+ Competencies + +
+ + +

+ Remove a competency [coming soon] +

+
+
+
+ DELETE + /api/competencies/{id} +
+
+
+
+
+
+

This call removes a competency from Command Centre.

+

Deleting competencies is coming to a future version of Command Centre.

+
+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

Deleting the competency failed. This happens when the item is still being used by another item.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

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

+
+
+
+
+
+
+
+

Card types

+
+
+

The card type API exists so that you can 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 it to a cardholder, so version 8.10 of the API added a page that returns the card types that your operator is allowed to assign to cardholders. 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.

+
+
+
+ + +
+ Card types + +
+ + +

+ Search card types +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
+ + +
+ Card types + +
+ + +

+ Search usable card types +

+
+
+
+ GET + /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. Perhaps there are none in the divisions in which your operator has 'View site', 'Configure site', or the other privileges that allow assign cards and credentials to cardholders.

+

You can find this URL in features.cardTypes.assignin /api. It was new in 8.10.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + division, + facilityCode, + availableCardStates, + credentialClass, + minimumNumber, + maximumNumber, + notes, + regex, + regexDescription + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The installation lacks a cardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/card_types/600",
+      "id": "600",
+      "name": "Red DESFire visitor badge",
+      "division": {
+        "id": "2",
+        "href": "https://localhost: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",
+      "defaultExpiry": "Reserved"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/card_types/assign?skip=1000"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Card types + +
+ + +

+ Get one card type +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+

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.

+

This means:

+
    +
  • +

    everything about a locker bank is in one place: lockers are components of a bank (there is no separate locker API), and its detail page shows all cardholder assignments, and

    +
  • +
  • +

    you cannot use the API to create or configure lockers or banks. There is no mention of 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 actually updating. The locker bank API supplies that URL.

+

Use cases

+

Displaying all banks and lockers, and the cardholders assigned to them

+
    +
  1. GET /api
  2. +
  3. Follow the link at features.lockerBanks.lockerBanks.href + , adding a search term to the query to thin out the results.
  4. +
  5. Follow the href of the locker bank you are after.
  6. +
+

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

    Find the href of the cardholder using the + cardholders API.

    +
  4. +
  5. +

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

    +
  6. +
+

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

+
    +
  1. GET /api
  2. +
  3. 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.
  4. +
  5. 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. If your operator is able to override that locker it will contain a field called href, giving the URL you need to + POST to open the locker.
  6. +
+
+
+
+ + +
+ Lockers + +
+ + +

+ Search locker banks +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.lockerBanks.lockerBanks section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Sets the maximum number of locker banks to return per page. The default depends on your server version; you should set it appropriately for your application.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + shortName, + description, + division, + lockers, + notes, + lockers.defaults, + lockers.id, + lockers.href, + lockers.name, + lockers.shortName, + lockers.description, + lockers.division, + lockers.notes, + lockers.commands, + lockers.assignments + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The installation lacks a lockers licence, or it is missing RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Lockers + +
+ + +

+ Get details of a locker bank +

+
+
+
+ GET + /api/locker_banks/{id} +
+
+
+
+
+
+

This returns details for a locker bank. Follow the href in the + locker bank summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

An internal identifier of the locker bank.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + shortName, + description, + division, + notes, + connectedController, + lockers, + lockers.defaults, + lockers.connectedController, + ... + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The installation lacks a lockers licence, or it is missing RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.

+
+
+
+
+
404 Not Found
+
+
+

Your REST operator does not have the privilege to view that locker bank.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+        }
+      ]
+    },
+    {
+      "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"
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+
+ + +
+ Lockers + +
+ + +

+ Get details of a locker +

+
+
+
+ GET + /api/lockers/{id} +
+
+
+
+
+
+

This returns details for a locker. Follow the href in the + locker bank summary or + locker bank detail to get here.

+

Prior to v8.20 this endpoint was at /api/locker_banks/{locker_bank_id}/lockers/{id} .

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

An internal identifier of the locker bank.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + shortName, + description, + division, + assignments, + notes, + commands, + connectedController + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Locker detail + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The installation lacks a lockers licence, or it is missing both RESTCardholders and RESTStatus and (in 8.60) RESTOverrides.

+
+
+
+
+
404 Not Found
+
+
+

Your REST operator does not have the privilege to view that locker bank ('Locker assignments').

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Lockers + +
+ + +

+ Open a locker +

+
+
+
+ POST + /api/lockers/{id}/open +
+
+
+
+
+
+

Sends an override to open a locker.

+

You should get this href from a + locker detail.

+

New in 8.10.1112.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the locker to open.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

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

+
+
+
+
+
+
+
+

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

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

    +
  4. +
  5. +

    Find your operator group.

    +
  6. +
  7. +

    The link at cardholders returns the cardholders who have membership of your group.

    +
  8. +
  9. +

    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.

    +
  10. +
+
+
+
+ + + + + +

+ Search operator groups +

+
+
+
+ GET + /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.

+

Added in 8.50.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Sets the maximum number of operator groups to return per page. The default depends on your server version; you should set it appropriately for your application.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + description, + division, + notes, + cardholders, + divisions + + + defaults +
+
+
+

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.

+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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 + /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.

+

Added in 8.50.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the operator group.

+
+
+
+
+
fields
+
in query
+
+ string[] + + defaults, + href, + id, + name, + description, + division, + notes, + cardholders, + divisions + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
404 Not 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').

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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 + /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.

+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The identifier of the operator group.

+
+
+
+
+
fields
+
in query
+
+ string[] + + defaults, + cardholder, + href + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
type
+ +
+
+
+
4xx
+
+
+

The ID is invalid, or it is valid but you do not have privileges for the operator group. Check the body of the result for a description of the problem.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "cardholders": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD",
+      "cardholder": {
+        "name": "Boothroyd, Algernon",
+        "href": "https://localhost:8904/api/cardholders/325"
+      }
+    },
+    {
+      "href": "https://localhost:8904/api/cardholders/329/operator_groups/AFADEMED",
+      "cardholder": {
+        "name": "Miles Messervy"
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+

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.

+

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 fields=accessGroups added to the query.
  2. +
  3. Iterate through the hrefs of those access groups, GETting each. That will return their detail pages.
  4. +
  5. 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).
  6. +
+

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

Setting a cardholder's PDF value

+

You do that by + PATCHing a cardholder with a field named after the PDF following an '@'.

+
+
+
+ + + + + +

+ Search PDF definitions +

+
+
+
+ GET + /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.

+

Take this URL from the href in the features.personalDataFields.personalDataFields section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + description, + division, + serverDisplayName, + type, + default, + required, + unique, + sortPriority, + accessGroups, + regex, + regexDescription, + defaultAccess, + operatorAccess, + notificationDefault + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders or RESTEvents licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "name": "email",
+      "id": "5516",
+      "href": "https://localhost:8904/api/personal_data_fields/5516"
+    },
+    {
+      "name": "cellphone",
+      "id": "9998",
+      "href": "https://localhost:8904/api/personal_data_fields/9998",
+      "serverDisplayName": "ruatoria.satellite.int"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/personal_data_fields?pos=900&sort=id"
+  }
+}
+
+ + +
+
+
+
+
+ + + + + +

+ Get details of a PDF definition +

+
+
+
+ GET + /api/personal_data_fields/{id} +
+
+
+
+
+
+

This returns details for a PDF definition. Follow the href in the + summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

An internal identifier for the PDF definition.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + id, + name, + description, + division, + serverDisplayName, + type, + default, + required, + unique, + sortPriority, + accessGroups, + regex, + regexDescription, + defaultAccess, + operatorAccess, + notificationDefault + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + PDF definition + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders or RESTEvents licence

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+    }
+  ],
+  "regex": ".*@.*",
+  "regexDescription": "@ least",
+  "notificationDefault": false,
+  "imageWidth": 600,
+  "imageHeight": 800,
+  "imageFormat": "jpg",
+  "contentType": "image/jpeg",
+  "isProfileImage": false
+}
+
+ + +
+
+
+
+

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. +
  3. 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.
  4. +
  5. Find the reception you're after and use its href in a visit.
  6. +
+
+
+
+ + +
+ Receptions + +
+ + +

+ Search receptions +

+
+
+
+ GET + /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.

+

Take this URL from the href in the features.receptions.receptions section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + name, + description, + division, + serverDisplayName, + defaultVisitorType, + notes + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Receptions + +
+ + +

+ Get details of a reception +

+
+
+
+ GET + /api/receptions/{id} +
+
+
+
+
+
+

This returns details for a reception. Follow the href in the + summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

An internal identifier for the reception.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + name, + description, + division, + serverDisplayName, + defaultVisitorType, + notes + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Reception + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders or RESTEvents licence

+
+
+
+
+
404 Not 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').

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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.

+
+
+
+ + +
+ Redactions + +
+ + +

+ Search redactions +

+
+
+
+ GET + /api/cardholders/redactions +
+
+
+
+
+
+

All the cardholders' redactions. Paginated.

+
+
+
+
+
+
+
+
+
cardholder
+
in query
+
+ string + +
+
+
+

Limits the results to redactions to cardholders with these IDs. Separate with commas.

+
+
+
+
+
status
+
in query
+
+ string + + pending, + inProgress, + cancelled, + done, + failed + + +
+
+
+

Limits the results to redactions with this status. Separate with commas.

+
+
+
+
+
type
+
in query
+
+ string + + normalEvents, + cardholder + + +
+
+
+

Limits the results to redactions of this type. Separate with commas.

+
+
+
+
+
after
+
in query
+
+ string + (date-time) + +
+
+
+

Limits the results to redactions that were scheduled to occur after this time.

+
+
+
+
+
before
+
in query
+
+ string + (date-time) + +
+
+
+

Limits the results to redactions that were scheduled to occur before this time.

+
+
+
+
+
fields
+
in query
+
+ string[] + + before cardholder finishTime href redactionOperator status type when + + +
+
+
+

Specifies the fields you want in the results.

+

Treat the string matches as case sensitive: use lastName rather than lastname.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

An array of redactions, and a link to the next page if any.

+
+
+
+
type
+
+ object + +
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Redactions + +
+ + +

+ Schedule a redaction +

+
+
+
+ POST + /api/cardholders/redactions +
+
+
+
+
+
+

Schedules a cardholder redaction.

+
+
+
+
+
+
+
+ +
+ +

Required fields are cardholder and type

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "cardholder": {
+    "href": "https://localhost:8904/api/cardholders/630"
+  },
+  "type": "normalEvents",
+  "when": "2023-01-01T00:00:00Z",
+  "before": "2022-01-01T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

The cardholder does not exist.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + +
+ Redactions + +
+ + +

+ Cancel a redaction +

+
+
+
+ DELETE + /api/cardholders/redactions +
+
+
+
+
+
+

Deletes a redaction record. Has to be pending.

+

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 redaction's effect on auditing.

+
+
+
+
+
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The operator does not have permissions to redact or to delete a cardholder, or the server is not licensed for cardholder operations.

+
+
+
+
+
404 Not Found
+
+
+

No such redaction, or your operator cannot see it.

+
+
+
+
+
+
+
+

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 you can change the cardholder's supervisor but not who the cardholder supervises.

+

Use case: searching for a role by name and assigning a relationship

+
    +
  1. GET /api
  2. +
  3. Follow the link at features.roles.roles.href + after adding search terms such as name="supervisor".
  4. +
  5. Find the href of the role with which you want to link your two cardholders.
  6. +
  7. Find the href of the cardholder who will perform this role (the supervisor).
  8. +
  9. Find the href of the cardholder for whom he or she will perform this role (the supervised).
  10. +
  11. + PATCH the second cardholder (the supervised) with the href of the first cardholder (the supervisor) and the href of the role. Here is an + example PATCH.
  12. +
+
+
+
+ + +
+ Roles + +
+ + +

+ Search roles +

+
+
+
+ GET + /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.

+

Take this URL from the href in the features.roles.roles section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Role search + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "name": "Supervisor",
+      "id": "1383",
+      "href": "https://localhost:8904/api/roles/1383"
+    },
+    {
+      "name": "Contract manager",
+      "id": "1399",
+      "href": "https://localhost:8904/api/roles/1399",
+      "serverDisplayName": "ruatoria.satellite.int"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/roles?pos=1000&sort=id"
+  }
+}
+
+ + +
+
+
+
+

Visits

+
+
+

Visits are new to 8.50.

+

Use case: finding a visit by name

+
    +
  1. GET /api
  2. +
  3. 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.
  4. +
  5. Find the visit you're after. You can then modify it by PATCHing its href.
  6. +
+

Use case: creating a visit

+
    +
  1. Find the href of the + reception at which your visitors will arrive.
  2. +
  3. Look at the visitor management configuration for your reception's + division.
  4. +
  5. 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.
  6. +
  7. Pick (or create) at least one cardholder for your visitor or visitors.
  8. +
  9. Build a + JSON payload containing all that.
  10. +
  11. POST to the link at features.visits.visits.href + (in the payload of the GET /api you did for the first step).
  12. +
+
+
+
+ + +
+ Visits + +
+ + +

+ Search visits +

+
+
+
+ GET + /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.

+

Take this URL from the href in the features.visits.visits section of /api.

+

Visits are new to 8.50.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + name, + description, + division, + serverDisplayName, + notes, + reception, + visitorType, + host, + from, + until, + location, + badgeText, + visitorAccessGroups, + visitors + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Visit search + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have both the RESTCardholders and VisitorManagement licences.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "name": "Supervisor",
+      "id": "1383",
+      "href": "https://localhost:8904/api/visits/1383"
+    },
+    {
+      "name": "Contract manager",
+      "id": "1399",
+      "href": "https://localhost:8904/api/visits/1399",
+      "serverDisplayName": "ruatoria.satellite.int"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/visits?pos=1000&sort=id"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Visits + +
+ + +

+ Create a visit +

+
+
+
+ POST + /api/visits +
+
+
+
+
+
+

Creates a new visit.

+

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)
  • +
+

Take this URL from the href in the features.visits.visits section of /api.

+

Like all the visit endpoints, this one is new to 8.50.

+
+
+
+
+
+
+
+
+ +
+
+ +

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.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "Greeter, 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": [
+    {
+      "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"
+    }
+  ]
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

The body of the POST did not describe a valid visit. Check the body of the server's response for hints.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have both the RESTCardholders and VisitorManagement licences.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new visit.

+
+ string + (url) + +
+
+
+
+
+
+
+ + +
+ Visits + +
+ + +

+ Get details of a visit +

+
+
+
+ GET + /api/visits/{id} +
+
+
+
+
+
+

This returns details for a visit. Follow the href in the + summary to get here.

+

All visit endpoints are new to 8.50.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

An internal identifier for the visit.

+
+
+
+
+
fields
+
in query
+
+ string[] + + href, + name, + description, + division, + serverDisplayName, + notes, + reception, + visitorType, + host, + from, + until, + location, + badgeText, + visitorAccessGroups, + visitors + + + defaults +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Visit + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have both the RESTCardholders and VisitorManagement licences.

+
+
+
+
+
404 Not 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').

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "name": "Greeter, 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": [
+    {
+      "accessGroup": {
+        "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"
+    }
+  ]
+}
+
+ + +
+
+
+
+
+ + +
+ Visits + +
+ + +

+ Modify a visit +

+
+
+
+ PATCH + /api/visits/{id} +
+
+
+
+
+
+

Changes an existing visit.

+

Follow the href in a + visit search to get here.

+

New to 8.50.

+
+
+
+
+
+
+
+
+ +
+
+ +

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.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "Greeter, Ginger",
+  "description": "Initial scoping",
+  "reception": {
+    "href": "https://localhost:8904/api/receptions/937"
+  },
+  "visitorType": {
+    "accessgroup": {
+      "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/937"
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

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

+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
404 Not 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').

+
+
+
+
+
+
+
+
+ + +
+ Visits + +
+ + +

+ Modify a visitor's status +

+
+
+
+ PATCH + /api/visits/{id}/visitors/{visitorId} +
+
+
+
+
+
+

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.

+

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.

+

Take this href from a + visit search or + visit.

+

New to 8.90.

+
+
+
+
+
+
+
+
+ +
+
+ +

This must contain a block called status containing a string called value, giving your desired state of the visitor.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "status": {
+    "value": "signingIn"
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
404 Not 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').

+
+
+
+
+
+
+
+

Schema Definitions

+ +
+

+ Cardholder summary: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+ id: + string + + +
+
+

An alphanumeric identifier, unique to the server. No API calls use cardholder IDs.

+

Deprecated. Use the href instead.

+
+
+ firstName: + string + +
+
+ lastName: + string + +
+
+ shortName: + string + + (up to 16 chars) +
+
+

If you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it.

+
+
+ description: + string + +
+
+ authorised: + boolean + +
+
+

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://localhost:8904/api/cardholders/325",
+  "id": "325",
+  "firstName": "Algernon",
+  "lastName": "Boothroyd",
+  "shortName": "Q",
+  "description": "Quartermaster",
+  "authorised": true
+}
+
+ + +
+
+
+
+
+

+ Cardholder detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ lastSuccessfulAccessTime: + string + (date-time) + + +
+
+

The date and time of the last successful card event.

+
+
+ lastSuccessfulAccessZone: + object + + +
+
+

The last zone the cardholder entered. 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.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ division: + object + +
+
+

The division containing this cardholder. You must send this when creating a cardholder.

+
+
+ @Student ID: + object + +
+
+

An example Personal Data Field value.

+

All PDFs except images appear at the top level of a cardholder in a field named after the PDF with a leading '@'.

+

Date PDFs will contain a date-time with the time part set to midnight.

+

Image PDFs appear as a block named after the PDF with a leading '@' containing an href. GETting that href will return the image data with an appropriate content-type header.

+

Put your PDF values here when creating or modifying a cardholder. Note that to hold a PDF value the cardholder must be a member of an access group that has that PDF attached to it. In this example, being a member of the special projects group has granted our Mr. Boothroyd the student ID and photo PDFs.

+

To set an image PDF, Base64-encode the image and send it as a string. There is one in the + cardholder POST example.

+

Date PDFs are only accurate to a day, so version 8.75 and later will ignore the time and time zone components if you send them. 8.70 and earlier will first perform a timezone correction then convert it to UTC before truncating the time, which might shift the date back a day if you specified a positive timezone offset. To be sure that does not happen on a server older than 8.75, put T00:00:00Z on the end of your date.

+
+
+ disableCipherPad: + boolean + +
+
+

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.

+
+
+ usercode: + string + +
+
+

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.

+
+
+ operatorLoginEnabled: + boolean + +
+
+

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.

+
+
+ operatorUsername: + string + +
+
+

The name that the operator uses when logging in to the interactive clients. Absent if blank.

+

Send the empty string "" to clear it.

+
+
+ operatorPassword: + string + +
+
+

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.

+
+
+ operatorPasswordExpired: + boolean + +
+
+

If true, the interactive clients will request a new password the next time this person logs in.

+
+
+ windowsLoginEnabled: + boolean + +
+
+

If true, and they have a Windows username, this user can log in using Windows integrated authentication.

+
+
+ windowsUsername: + string + +
+
+

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.

+
+
+ personalDataDefinitions: + + + Cardholder PDF + + + +
+
+

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

+
+
+
+ + + Cardholder PDF + + + +
+
+
+
+ cards: + + + Cardholder card + + + +
+
+

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.

+
+
+
+ + + Cardholder card + + + +
+
+
+
+ accessGroups: + + + Cardholder access group + + + +
+
+

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

+
+
+
+ + + Cardholder access group + + + +
+
+
+
+ operatorGroups: + + + Cardholder operator group + + + +
+
+

All the cardholder's operator group memberships. Added in 8.50.

+
+
+
+ + + Cardholder operator group + + + +
+
+
+
+ competencies: + + + Cardholder competency + + + +
+
+

Every competency a cardholder possesses. There is quite a lot there: see the schema definition for detailed explanation.

+
+
+
+ + + Cardholder competency + + + +
+
+
+
+ edit: + object + (url) + + +
+
+

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.

+
+
+ updateLocation: + object + + +
+
+

Link to + POST to when you want to update the location of this cardholder. Added in 8.20.

+
+
+ notes: + string + +
+
+

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.

+
+
+ notifications: + object + +
+
+

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

+
+
+ relationships: + + + Cardholder relationship + + + +
+
+

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.

+
+
+
+ + + Cardholder relationship + + + +
+
+
+
+ lockers: + + + Cardholder locker + + + +
+
+

All the cardholder's locker assignments.

+
+
+
+ + + Cardholder locker + + + +
+
+
+
+ elevatorGroups: + + + Cardholder elevator group + + + +
+
+

The cardholder's elevator group properties. They may have one per elevator group.

+

Added in 8.50.

+
+
+
+ + + Cardholder elevator group + + + +
+
+
+
+ updates: + object + +
+
+

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.

+
+
+ redactions: + + + Cardholder redaction + + + +
+
+

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.

+
+
+
+ + + Cardholder redaction + + + +
+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325",
+  "id": "325",
+  "firstName": "Algernon",
+  "lastName": "Boothroyd",
+  "shortName": "Q",
+  "description": "Quartermaster",
+  "authorised": true,
+  "lastSuccessfulAccessTime": "2004-11-18T19:21:52Z",
+  "lastSuccessfulAccessZone": {
+    "href": "https://localhost:8904/api/access_zones/333",
+    "name": "Twilight zone"
+  },
+  "serverDisplayName": "ruatoria.satellite.int",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "@Student ID": "8904640",
+  "disableCipherPad": false,
+  "usercode": "numeric, and write-only",
+  "operatorLoginEnabled": true,
+  "operatorUsername": "qmaster",
+  "operatorPassword": "write-only",
+  "operatorPasswordExpired": false,
+  "windowsLoginEnabled": true,
+  "windowsUsername": "misix.local\\qmaster",
+  "personalDataDefinitions": [
+    {
+      "@Student ID": {
+        "href": "https://localhost:8904/api/cardholders/325/personal_data/2356",
+        "definition": {
+          "href": "https://localhost:8904/api/personal_data_fields/2356",
+          "name": "Student ID",
+          "id": "2356",
+          "type": "string"
+        },
+        "value": "8904640"
+      }
+    },
+    {
+      "@Photo": {
+        "href": "https://localhost:8904/api/cardholders/325/personal_data/2369",
+        "definition": {
+          "href": "https://localhost:8904/api/personal_data_fields/2369",
+          "name": "Photo",
+          "id": "2369",
+          "type": "image"
+        },
+        "value": {
+          "href": "https://localhost:8904/api/cardholders/325/personal_data/2369"
+        }
+      }
+    }
+  ],
+  "cards": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9",
+      "number": "1",
+      "cardSerialNumber": "045A5769713E80",
+      "issueLevel": 1,
+      "status": {
+        "value": "Disabled (manually)",
+        "type": "inactive"
+      },
+      "type": {
+        "href": "https://localhost:8904/api/card_types/354",
+        "name": "Card type no. 1"
+      },
+      "invitation": {
+        "email": "nick@example.com",
+        "mobile": "02123456789",
+        "singleFactorOnly": true,
+        "status": "sent",
+        "href": "https://security.gallagher.cloud/api/invitations/abcd1234defg5678"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2017-12-31T11:59:59Z",
+      "credentialClass": "mobile",
+      "trace": false,
+      "lastPrintedOrEncodedTime": "2020-08-10T09:20:50Z",
+      "lastPrintedOrEncodedIssueLevel": 1,
+      "pin": "153624",
+      "visitorContractor": false,
+      "ownedBySite": false,
+      "credentialId": "reserved",
+      "bleFacilityId": "reserved"
+    }
+  ],
+  "accessGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/access_groups/D714D8A89F",
+      "accessGroup": {
+        "name": "R&D special projects group",
+        "href": "https://localhost:8904/api/access_groups/352"
+      },
+      "status": {
+        "value": "Pending",
+        "type": "pending"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2017-12-31T11:59:59Z"
+    }
+  ],
+  "operatorGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD",
+      "operatorGroup": {
+        "name": "Locker admins",
+        "href": "https://localhost:8904/api/operator_groups/532"
+      }
+    }
+  ],
+  "competencies": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/competencies/2dc3p0",
+      "competency": {
+        "href": "https://localhost:8904/api/competencies/2354",
+        "name": "Hazardous goods handling"
+      },
+      "status": {
+        "value": "Pending",
+        "type": "pending"
+      },
+      "expiryWarning": "2017-03-06T15:45:00Z",
+      "expiry": "2017-03-09T15:45:00Z",
+      "enablement": "2018-03-09T15:45:00Z",
+      "comment": "CPR refresher due March.",
+      "limitedCredit": true,
+      "credit": 37
+    }
+  ],
+  "edit": {
+    "href": "https://localhost:8904/cardholders/325/edit"
+  },
+  "updateLocation": {
+    "href": "https://localhost:8904/api/cardholders/402/update_location"
+  },
+  "notes": "",
+  "notifications": {
+    "enabled": true,
+    "from": "2017-10-10T14:59:00Z",
+    "until": "2017-10-17T14:59:00Z"
+  },
+  "relationships": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/relationships/179lah1170",
+      "role": {
+        "href": "https://localhost:8904/api/roles/5396",
+        "name": "Supervisor"
+      },
+      "cardholder": {
+        "href": "https://localhost:8904/api/cardholders/5398",
+        "name": "Miles Messervy",
+        "firstName": "Miles",
+        "lastName": "Messervy"
+      }
+    }
+  ],
+  "lockers": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/lockers/t1m4",
+      "locker": {
+        "name": "Bank A locker 1",
+        "shortName": "A1",
+        "lockerBank": {
+          "href": "https://localhost:8904/api/locker_banks/4567",
+          "name": "Bank A"
+        },
+        "href": "https://localhost:8904/api/lockers/3456"
+      },
+      "from": "2017-01-01T00:00:00Z",
+      "until": "2018-12-31T00:00:00Z"
+    }
+  ],
+  "elevatorGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/elevator_groups/567",
+      "elevatorGroup": {
+        "href": "https://localhost:8904/api/elevator_groups/635",
+        "name": "Main building lower floors"
+      },
+      "accessZone": {
+        "href": "https://localhost: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": "object",
+  "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"
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Cardholder POST example: + +

+
+
+
+

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, and two lockers.

+

There are plenty more fields than shown in this example. For a complete list please see the schema for the detailed + cardholder object that you receive from a cardholder href.

+
+
+
+
+ firstName: + string + +
+
+

You must supply either this or the last name when creating a cardholder.

+
+
+ lastName: + string + +
+
+

You must supply either this or the first name when creating a cardholder.

+
+
+ shortName: + string + +
+
+ description: + string + +
+
+ authorised: + boolean + +
+
+

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.

+
+
+ division: + object + + +
+
+

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

+
+
+ @email: + string + +
+
+

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

+
+
+ @headshot: + string + +
+
+

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

+
+
+ personalDataDefinitions: + + + Cardholder PDF + + + +
+
+

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.

+
+
+
+ + + Cardholder PDF + + + +
+
+
+
+ cards: + + + Cardholder card + + + +
+
+

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.

+
+
+
+ + + Cardholder card + + + +
+
+
+
+ accessGroups: + + + Cardholder access group + + + +
+
+

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.

+
+
+
+ + + Cardholder access group + + + +
+
+
+
+ operatorGroups: + + + Cardholder operator group + + + +
+
+

Here you can add the operator groups necessary to give your new cardholder the software access he or she needs. Added in 8.50.

+
+
+
+ + + Cardholder operator group + + + +
+
+
+
+ competencies: + + + Cardholder competency + + + +
+
+

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

+
+
+
+ + + Cardholder competency + + + +
+
+
+
+ notes: + string + +
+
+ notifications: + object + +
+
+

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

+
+
+ relationships: + + + Cardholder relationship + + + +
+
+

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.

+
+
+
+ + + Cardholder relationship + + + +
+
+
+
+ lockers: + + + Cardholder locker + + + +
+
+

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.

+
+
+
+ + + Cardholder locker + + + +
+
+
+
+ elevatorGroups: + + + Cardholder elevator group + + + +
+
+

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.

+
+
+
+ + + Cardholder elevator group + + + +
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "firstName": "Algernon",
+  "lastName": "Boothroyd",
+  "shortName": "Q",
+  "description": "Quartermaster",
+  "authorised": true,
+  "division": {
+    "href": "https://localhost:8904/api/divisions/5387"
+  },
+  "@email": "user@sample.com",
+  "@headshot": "/9j/4A...==",
+  "personalDataDefinitions": [
+    {
+      "@email": {
+        "notifications": true
+      }
+    }
+  ],
+  "cards": [
+    {
+      "type": {
+        "href": "https://localhost:8904/api/card_types/600"
+      },
+      "pin": "153624"
+    },
+    {
+      "type": {
+        "href": "https://localhost:8904/api/card_types/654"
+      },
+      "number": "Nick's mobile",
+      "invitation": {
+        "email": "nick@example.com",
+        "mobile": "02123456789",
+        "singleFactorOnly": true
+      }
+    }
+  ],
+  "accessGroups": [
+    {
+      "accessgroup": {
+        "href": "https://localhost:8904/api/access_groups/352"
+      },
+      "from": "2019-01-01"
+    }
+  ],
+  "operatorGroups": [
+    {
+      "operatorgroup": {
+        "href": "https://localhost:8904/api/operator_groups/523"
+      }
+    }
+  ],
+  "competencies": [
+    {
+      "competency": {
+        "href": "https://localhost:8904/api/competencies/2354"
+      },
+      "enabled": false,
+      "enablement": "2019-01-01"
+    }
+  ],
+  "notes": "",
+  "notifications": {
+    "enabled": true,
+    "from": "2017-10-10T14:59:00Z",
+    "until": "2017-10-17T14:59:00Z"
+  },
+  "relationships": [
+    {
+      "role": {
+        "href": "https://localhost:8904/api/roles/5396"
+      },
+      "cardholder": {
+        "href": "https://localhost:8904/api/cardholders/5398"
+      }
+    }
+  ],
+  "lockers": [
+    {
+      "locker": {
+        "href": "https://localhost:8904/api/lockers/3456"
+      }
+    },
+    {
+      "locker": {
+        "href": "https://localhost:8904/api/lockers/3457"
+      }
+    }
+  ],
+  "elevatorGroups": [
+    {
+      "elevatorGroup": {
+        "href": "https://localhost:8904/api/elevator_groups/635"
+      },
+      "accessZone": {
+        "href": "https://localhost:8904/api/access_zones/637"
+      }
+    },
+    {
+      "elevatorGroup": {
+        "href": "https://localhost:8904/api/elevator_groups/639"
+      },
+      "enableCodeBlueFeatures": true
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Cardholder Update Location POST example: + +

+
+
+
+

This is an example of a POST you could use to move a cardholder to a target access zone.

+
+
+
+
+ accessZone: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

The href of the access zone into which you want to move your cardholder. This example came from the + access zones controller, but you can also use an href from the + items controller if you do not have a RESTStatus or (in 8.60) RESTOverrides licence.

+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "accessZone": {
+    "href": "https://localhost:8904/api/access_zones/412"
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder PATCH example: + +

+
+
+
+

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, groups, and relationships.

+

All the cardholder fields you can supply are described in the + cardholder detail.

+

This example:

+
    +
  • sets 'authorised' true,
  • +
  • sets a PDF holding an employee ID,
  • +
  • changes the notification flags on two PDFs,
  • +
  • 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.
  • +
+

This is also the method you use for changing a cardholder's name, description, notes, user code, etc.

+

In addition to those fields, this PATCH format accepts five arrays: 'cards', 'accessGroups', 'competencies', 'relationships', and (in 8.50) 'operatorGroups'.

+
+
+
+
+ authorised: + boolean + +
+
+

You can modify + all the fields on a cardholder, provided you have the necessary privileges,

+
+
+ @employeeId: + string + +
+
+

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 he or she is not a member of the access group that grants that PDF. In this example, one or both of access groups 352 and 124 are attached to 'employeeId'.

+
+
+ personalDataDefinitions: + + + Cardholder PDF + + + +
+
+

This is how you set and unset the notifications flags on PDFs. Presumably this cardholder wants to receive notifications at the email address stored in the 'email' PDF rather than by SMS.

+
+
+
+ + + Cardholder PDF + + + +
+
+
+
+ cards: + object + +
+
+

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.

+
+
+ accessGroups: + object + +
+
+

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.

+

Each element of the 'update' array will need an href identifying the membership to update, and one or both of the 'from' and 'until' date-times containing new values (you cannot change the group--just the dates). 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.

+
+
+ competencies: + object + +
+
+

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.

+
+
+ relationships: + object + +
+
+

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.

+
+
+ lockers: + object + +
+
+

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.

+
+
+ operatorGroups: + object + +
+
+

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.

+
+
+ elevatorGroups: + object + +
+
+

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,
+  "@employeeId": "THX1139",
+  "personalDataDefinitions": [
+    {
+      "@email": {
+        "notifications": true
+      }
+    },
+    {
+      "@cellphone": {
+        "notifications": false
+      }
+    }
+  ],
+  "cards": {
+    "add": [
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/354"
+        },
+        "pin": "153624"
+      },
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/600"
+        },
+        "number": "Jock's iPhone 8",
+        "status": {
+          "value": "Pending sign-off"
+        },
+        "invitation": {
+          "email": "jock@example.com"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
+        "issueLevel": 2,
+        "until": "",
+        "status": {
+          "value": "Stolen"
+        },
+        "pin": "153624"
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/77e8affe7c7e4b56",
+        "status": {
+          "value": "Lost"
+        }
+      }
+    ]
+  },
+  "accessGroups": {
+    "add": [
+      {
+        "accessGroup": {
+          "href": "https://localhost:8904/api/access_groups/352"
+        }
+      },
+      {
+        "accessGroup": {
+          "href": "https://localhost:8904/api/access_groups/124"
+        },
+        "until": "2019-12-31"
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/access_groups/10ad21",
+        "until": ""
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/access_groups/10ed27"
+      }
+    ]
+  },
+  "competencies": {
+    "add": [
+      {
+        "competency": {
+          "href": "https://localhost:8904/api/competencies/2354"
+        },
+        "enabled": false,
+        "enablement": "2021-01-01T08:00+13"
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/competencies/2dc3",
+        "enabled": true
+      }
+    ]
+  },
+  "relationships": {
+    "add": [
+      {
+        "role": {
+          "href": "https://localhost:8904/api/roles/5396"
+        },
+        "cardholder": {
+          "href": "https://localhost:8904/api/cardholders/5398"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/roles/1799lah1170",
+        "cardholder": {
+          "href": "https://localhost:8904/api/cardholders/10135"
+        }
+      }
+    ]
+  },
+  "lockers": {
+    "add": [
+      {
+        "locker": {
+          "href": "https://localhost:8904/api/lockers/1200",
+          "from": "2019-01-01"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/lockers/wxyz1234",
+        "until": "2020-02-29"
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/lockers/abcd4321"
+      }
+    ]
+  },
+  "operatorGroups": {
+    "add": [
+      {
+        "operatorGroup": {
+          "href": "https://localhost:8904/api/operator_groups/532"
+        }
+      },
+      {
+        "operatorGroup": {
+          "href": "https://localhost:8904/api/operator_groups/535"
+        }
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD"
+      }
+    ]
+  },
+  "elevatorGroups": {
+    "add": [
+      {
+        "elevatorGroup": {
+          "href": "https://localhost:8904/api/elevator_groups/635"
+        },
+        "accessZone": {
+          "href": "https://localhost:8904/api/access_zones/637"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/elevator_groups/1268613268",
+        "enableCodeBlueFeatures": true,
+        "enableVipFeatures": false
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/elevator_groups/3498734"
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder PDF: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

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. If the PDF is not an image the content type will be text/plain, encoded (like everything else in this API) as UTF-8.

+

It is read-only: do not send it in a PATCH or POST.

+
+
+ definition: + object + +
+
+

The definition object is read-only: do not send it in a PATCH or POST.

+
+
+
+
+
+ href: + string + (url) + +
+
+

This is the href of the PDF's definition, common to all cardholders who hold a value for this PDF.

+
+
+ name: + string + +
+
+

The PDF's name, without an '@' prefix.

+
+
+ id: + string + +
+
+

Short alphanumeric identifier used elsewhere in the API to filter cardholder searches and add PDF values to alarm and event GETs.

+
+
+ type: + string + + string, + image, + strEnum, + numeric, + date, + address, + phone, + email, + mobile + + +
+
+
+
+
+ value: + string + +
+
+

In a GET response for text and numeric PDFs this will be a scalar containing their actual value, but for image PDFs it will be an object containing a copy of the href field above.

+

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.

+
+
+ notifications: + boolean + +
+
+

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
+ + + +
{
+  "@cellphone": {
+    "href": "https://localhost:8904/api/cardholders/325/personal_data/9998",
+    "definition": {
+      "href": "https://localhost:8904/api/personal_data_fields/9998",
+      "name": "cellphone",
+      "id": "9998",
+      "type": "mobile"
+    },
+    "value": "a@b.com",
+    "notifications": false
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder card: + +

+
+
+
+

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 + document of their own.

+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ number: + string + +
+
+

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.

+
+
+ cardSerialNumber: + string + +
+
+

The serial number (CSN) of a physical access card as a hex string without a leading '0x'.

+

The example in this example shows the byte ordering you can expect in a GET and should use in a PATCH or POST. The card serials (UIDs) of NXP MIFARE cards start with 04 and end with 80.

+

This may not be not present on older cards.

+

This field was read-only until 7.90. It is read-write in 8.00 and later.

+
+
+ issueLevel: + integer + 0 ≤ x ≤ 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.

+
+
+ status: + object + +
+
+

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.

+
+
+
+
+
+ value: + string + +
+
+

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 have extra values covered + in the PIV supplement.

+

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.

+

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 ('active', for the factory state set).

+
+
+ type: + string + + pending, + active, + expired, + inactive + + + +
+
+

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.

+
+
+
+
+
+ type: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+ name: + string + +
+
+
+
+
+ invitation: + object + +
+
+

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 dependence on cellular connectivity is undesirable. 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.

+
+
+
+
+
+ email: + string + +
+
+

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

+
+
+ mobile: + string + +
+
+

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

+
+
+ singleFactorOnly: + boolean + + false +
+
+

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.

+
+
+ status: + string + + notSent, + sent, + expired, + accepted + + + +
+
+
    +
  • +

    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.

    +
  • +
+
+
+ href: + string + (url) + + +
+
+

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

+
+
+
+
+
+ from: + string + (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.

+
+
+ until: + string + (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.

+
+
+ credentialClass: + string + + card, + digitalId, + govPass, + mobile, + piv, + pivi, + trackingTag, + transact + + + +
+
+

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.

+
+
+ trace: + boolean + +
+
+

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.

+
+
+ lastPrintedOrEncodedTime: + string + (date-time) + + +
+
+

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.

+
+
+ lastPrintedOrEncodedIssueLevel: + integer + 1 ≤ x ≤ 15 + +
+
+

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.

+
+
+ pin: + string + +
+
+

Even though this appears in the example of a cardholder GET, 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 when the server converts them to integers. Putting quotes around the PIN forces the server to treat them as strings, which preserves any leading zeros.

+

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.

+
+
+ visitorContractor: + boolean + +
+
+

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.

+
+
+ ownedBySite: + boolean + + +
+
+

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.

+
+
+ credentialId: + object + +
+
+

Reserved for use by Gallagher applications.

+
+
+ bleFacilityId: + object + +
+
+

Reserved for use by Gallagher applications.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9",
+  "number": "1",
+  "cardSerialNumber": "045A5769713E80",
+  "issueLevel": 1,
+  "status": {
+    "value": "Disabled (manually)",
+    "type": "inactive"
+  },
+  "type": {
+    "href": "https://localhost:8904/api/card_types/354",
+    "name": "Card type no. 1"
+  },
+  "invitation": {
+    "email": "nick@example.com",
+    "mobile": "02123456789",
+    "singleFactorOnly": true,
+    "status": "sent",
+    "href": "https://security.gallagher.cloud/api/invitations/abcd1234defg5678"
+  },
+  "from": "2017-01-01T00:00:00Z",
+  "until": "2017-12-31T11:59:59Z",
+  "credentialClass": "mobile",
+  "trace": false,
+  "lastPrintedOrEncodedTime": "2020-08-10T09:20:50Z",
+  "lastPrintedOrEncodedIssueLevel": 1,
+  "pin": "153624",
+  "visitorContractor": false,
+  "ownedBySite": false,
+  "credentialId": "reserved",
+  "bleFacilityId": "reserved"
+}
+
+ + +
+
+
+
+
+

+ Cardholder card mobile example: + +

+
+
+
+

This is an example of what you would send to create a new mobile credential. The + card detail contains everything in a card, and is a bit daunting, so this example shows only the fields you need for a mobile credential.

+

You can submit it as part of a larger document in a + POST to create a cardholder with cards, or as part of a + PATCH to create a mobile credential on an existing cardholder.

+
+
+
+
+ number: + string + +
+
+

The so-called card 'number' on a mobile credential does not need to be numeric.

+
+
+ status: + object + +
+
+

Optional. Provide a field value set to one of the valid starting states for the mobile credential. If you omit it, the credential will start in the default state.

+
+
+ type: + object + +
+
+

This is the only compulsory field in the POST body. It must contain an href to the card type of the new mobile credential.

+
+
+ from: + string + (date-time) + +
+
+ until: + string + (date-time) + +
+
+ invitation: + object + +
+
+

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

+
+
+
+
+
+ email: + string + +
+
+ mobile: + string + +
+
+ singleFactorOnly: + boolean + +
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "number": "Nick's mobile",
+  "status": {
+    "value": "active"
+  },
+  "type": {
+    "href": "https://localhost:8904/api/card_types/654"
+  },
+  "from": "2017-01-01T00:00:00Z",
+  "until": "2018-01-01T00:00:00Z",
+  "invitation": {
+    "email": "nick@example.com",
+    "mobile": "02123456789",
+    "singleFactorOnly": true
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder card physical example: + +

+
+
+
+

This is a minimal object for creating a card, containing the only required field: the card type. See the + card detail for other fields you can use, such as the card's issue level and from/until dates.

+

You can submit it as part of a larger document in a + POST to create a cardholder with cards, or as part of a + PATCH to assign a card to an existing cardholder.

+
+
+
+
+ type: + object + +
+
+

This is the only compulsory field in the POST body. It must contain an href to the card type of the new card.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "type": {
+    "href": "https://localhost:8904/api/card_types/600"
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder access group: + +

+
+
+
+

An + access group is an object in Command Centre. The connection between an access group and a cardholder is 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.

+
+
+
+
+ href: + string (url) + +
+
+

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 will always return a 404.

+
+
+ accessGroup: + object + + +
+
+

An object containing a link to this group's detail page and its name. It is read-only because you cannot change a group membership's group.

+

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

+
+
+ status: + object + + +
+
+

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

+
+
+
+
+
+ value: + string + + +
+
+

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.

+
+
+ type: + string + + pending, + active, + expired + + + +
+
+

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

+
+
+
+
+
+ from: + string + (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. In 7.90.883 or earlier, this should always be in UTC with a trailing 'Z'.

+
+
+ until: + string + (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. In 7.90.883 or earlier, this should always be in UTC with a trailing 'Z'.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325/access_groups/D714D8A89F",
+  "accessGroup": {
+    "name": "R&D special projects group",
+    "href": "https://localhost:8904/api/access_groups/352"
+  },
+  "status": {
+    "value": "Pending",
+    "type": "pending"
+  },
+  "from": "2017-01-01T00:00:00Z",
+  "until": "2017-12-31T11:59:59Z"
+}
+
+ + +
+
+
+
+
+

+ Cardholder operator group: + +

+
+
+
+

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.

+
+
+
+
+ href: + string (url) + +
+
+

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.

+
+
+ operatorGroup: + object + + +
+
+

An object containing a link to this operator group's detail page and its name. It is marked read-only because you do not send it to the server when managing a cardholder's operator groups: you cannot change a group membership's group.

+

The link will be absent if your operator does not have the privilege to view the operator group ('View operators' or 'Edit operators', for example).

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD",
+  "operatorGroup": {
+    "name": "Locker admins",
+    "href": "https://localhost:8904/api/operator_groups/532"
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder competency: + +

+
+
+
+

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.

+

The REST API allows you to link 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 is a block in the cardholder detail. You can also put it in the results of a cardholder search by putting competencies in the fields parameter.

+

This section describes what you receive from a GET. The + Cardholder competency update describes what you should send in a POST or PATCH to set or update a cardholder's link to a competency.

+
+
+
+
+ href: + string + (url) + + +
+
+

Use this URL 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 always return a 404.

+
+
+ competency: + object + + +
+
+

This contains the competency's name and its href. They are read only because they belong to the competency itself, not the cardholder's link to it.

+
+
+ status: + object + + +
+
+

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 competency. Whether it is + expired is derived from an expiry date (actually a timestamp accurate to the second): if it is in the past, Command Centre considers the competency expired.

+

A competency can also have an 'enable' date. If that date (timestamp) passes while the competency is disabled, Command Centre will enable it. It will leave the enable 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 'expires' 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
falsenull-inactive
+
+
+ expiryWarning: + string + (date-time) + + +
+
+

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

+
+
+ expiry: + string + (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'.

+
+
+ enablement: + string + (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'.

+
+
+ comment: + string + +
+
+

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

+
+
+ limitedCredit: + boolean + +
+
+

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.

+
+
+ credit: + integer + +
+
+

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://localhost:8904/api/cardholders/325/competencies/2dc3p0",
+  "competency": {
+    "href": "https://localhost:8904/api/competencies/2354",
+    "name": "Hazardous goods handling"
+  },
+  "status": {
+    "value": "Pending",
+    "type": "pending"
+  },
+  "expiryWarning": "2017-03-06T15:45:00Z",
+  "expiry": "2017-03-09T15:45:00Z",
+  "enablement": "2018-03-09T15:45:00Z",
+  "comment": "CPR refresher due March.",
+  "limitedCredit": true,
+  "credit": 37
+}
+
+ + +
+
+
+
+
+

+ Cardholder competency update: + +

+
+
+
+

This is a description of the fields you would supply when updating a cardholder's holding of a competency. It is slightly different from what you receive in a cardholder GET: you do not send read-only blocks such the status and the competency, and there is a write-only field 'enabled' that you can supply to change the status.

+

You cannot change the competency to which the status, times, comment, and credit applies. 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 raise a stateful alarm depending on your version of Command Centre.

+
+
+
+
+ href: + string + (url) + + +
+
+

Required. This identifies the cardholder competency you are updating.

+
+
+ enabled: + boolean + +
+
+

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 to disable the competency now, particularly if you are setting a future enablement date.

+
+
+ expiry: + string + (date-time) + +
+
+

The time at which you want to expire the competency. See the full description + above.

+
+
+ enablement: + string + (date-time) + +
+
+

The time at which you want to re-enable the competency. You can set this on an enabled competency but it has no effect there, so with this field you would generally also set enabled to false. See the full description + above.

+
+
+ comment: + string + +
+
+ limitedCredit: + boolean + +
+
+

If you set this false, Command Centre's 'Pre-pay Car Parking' feature (available under its own licence) will not reduce the current credit.

+
+
+ credit: + integer + +
+
+

The amount of credit left on this competency for use by Pre-pay Car Parking. It can be negative.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325/competencies/2dc3p0",
+  "enabled": true,
+  "expiry": "2017-03-09T15:45:00Z",
+  "enablement": "2018-03-09T15:45:00Z",
+  "comment": "CPR refresher due March.",
+  "limitedCredit": true,
+  "credit": 37
+}
+
+ + +
+
+
+
+
+

+ Cardholder relationship: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ role: + object + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

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://localhost:8904/api/cardholders/325/relationships/179lah1170",
+  "role": {
+    "href": "https://localhost:8904/api/roles/5396",
+    "name": "Supervisor"
+  },
+  "cardholder": {
+    "href": "https://localhost:8904/api/cardholders/5398",
+    "name": "Miles Messervy",
+    "firstName": "Miles",
+    "lastName": "Messervy"
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder locker: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ locker: + object + +
+
+

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.

+
+
+ from: + string + (date-time) + +
+
+

The start of the time period during which the cardholder has access to this locker.

+

Send an empty string "" to reset it.

+
+
+ until: + string + (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://localhost:8904/api/cardholders/325/lockers/t1m4",
+  "locker": {
+    "name": "Bank A locker 1",
+    "shortName": "A1",
+    "lockerBank": {
+      "href": "https://localhost:8904/api/locker_banks/4567",
+      "name": "Bank A"
+    },
+    "href": "https://localhost:8904/api/lockers/3456"
+  },
+  "from": "2017-01-01T00:00:00Z",
+  "until": "2018-12-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+

+ Cardholder elevator group: + +

+
+
+
+

These appear in an array in a cardholder detail, showing the 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.

+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+ elevatorGroup: + object + +
+
+

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

+
+
+ accessZone: + object + +
+
+

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.

+
+
+ enableCaptureFeatures: + boolean + +
+
+

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.

+
+
+ enableCodeBlueFeatures: + boolean + +
+
+

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.

+
+
+ enableExpressFeatures: + boolean + +
+
+

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.

+
+
+ enableServiceFeatures: + boolean + +
+
+

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.

+
+
+ enableService2Features: + boolean + +
+
+

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.

+
+
+ enableService3Features: + boolean + +
+
+

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.

+
+
+ enableVipFeatures: + boolean + +
+
+

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://localhost:8904/api/cardholders/325/elevator_groups/567",
+  "elevatorGroup": {
+    "href": "https://localhost:8904/api/elevator_groups/635",
+    "name": "Main building lower floors"
+  },
+  "accessZone": {
+    "href": "https://localhost:8904/api/access_zones/637",
+    "name": "Lvl 1 lift lobby"
+  },
+  "enableCaptureFeatures": true,
+  "enableCodeBlueFeatures": false,
+  "enableExpressFeatures": true,
+  "enableServiceFeatures": false,
+  "enableService2Features": true,
+  "enableService3Features": true,
+  "enableVipFeatures": false
+}
+
+ + +
+
+
+
+
+

+ Cardholder changes: + +

+
+
+
+

An array of cardholder changes, described in the next section, and a next link for more.

+
+
+
+
+ results: + + + Cardholder change + + + +
+
+

An array of cardholder changes.

+
+
+
+ + + Cardholder change + + + +
+
+
+
+ next: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/cardholders/changes/f4e67a",
+      "time": "2020-01-14T03:14:33Z",
+      "type": "update",
+      "item": {
+        "href": "https://localhost:8904/api/cardholders/525"
+      },
+      "operator": {
+        "name": "System Operator",
+        "href": "https://localhost:8904/api/items/1"
+      },
+      "oldValues": {
+        "firstName": "Craig",
+        "competencies": [
+          {
+            "enablement": "",
+            "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4"
+          }
+        ]
+      },
+      "newValues": {
+        "firstName": "Gavin",
+        "competencies": [
+          {
+            "enablement": "2020-02-29T00:00:00Z",
+            "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4"
+          }
+        ],
+        "cards": [
+          {
+            "number": "2",
+            "cardSerialNumber": "",
+            "issueLevel": 1,
+            "from": "",
+            "until": "",
+            "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba"
+          }
+        ],
+        "accessGroups": [
+          {
+            "accessGroup": {
+              "name": "Access Group 1",
+              "href": "https://localhost:8904/api/access_groups/499"
+            },
+            "from": "",
+            "until": "2020-01-15T04:39:00Z",
+            "href": "https://localhost:8904/api/cardholders/525/access_groups/f9cb328b4"
+          }
+        ]
+      },
+      "cardholder": {
+        "firstName": "Gavin",
+        "cards": [
+          {
+            "number": "2",
+            "cardSerialNumber": "",
+            "issueLevel": 1,
+            "from": "",
+            "until": "",
+            "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba"
+          }
+        ]
+      }
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/cardholders/changes?pos=SWEp9"
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder change: + +

+
+
+
+

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

    +
  • +
+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ time: + string + (date-time) + +
+
+

The time that this change occurred.

+
+
+ type: + string + + add, + update, + remove + + +
+
+

'add' if this change added a cardholder, 'update' if it modified a cardholder, or 'remove' if it deleted a cardholder.

+
+
+ item: + object + +
+
+

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

+
+
+ operator: + object + +
+
+

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.

+
+
+ oldValues: + object + +
+
+

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.

+
+
+ newValues: + object + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

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://localhost:8904/api/cardholders/changes/f4e67a",
+  "time": "2020-01-14T03:14:33Z",
+  "type": "update",
+  "item": {
+    "href": "https://localhost:8904/api/cardholders/525"
+  },
+  "operator": {
+    "name": "System Operator",
+    "href": "https://localhost:8904/api/items/1"
+  },
+  "oldValues": {
+    "firstName": "Craig",
+    "competencies": [
+      {
+        "enablement": "",
+        "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4"
+      }
+    ]
+  },
+  "newValues": {
+    "firstName": "Gavin",
+    "competencies": [
+      {
+        "enablement": "2020-02-29T00:00:00Z",
+        "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4"
+      }
+    ],
+    "cards": [
+      {
+        "number": "2",
+        "cardSerialNumber": "",
+        "issueLevel": 1,
+        "from": "",
+        "until": "",
+        "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba"
+      }
+    ],
+    "accessGroups": [
+      {
+        "accessGroup": {
+          "name": "Access Group 1",
+          "href": "https://localhost:8904/api/access_groups/499"
+        },
+        "from": "",
+        "until": "2020-01-15T04:39:00Z",
+        "href": "https://localhost:8904/api/cardholders/525/access_groups/f9cb328b4"
+      }
+    ]
+  },
+  "cardholder": {
+    "firstName": "Gavin",
+    "cards": [
+      {
+        "number": "2",
+        "cardSerialNumber": "",
+        "issueLevel": 1,
+        "from": "",
+        "until": "",
+        "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba"
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+

+ Cardholder redaction: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

DELETE this URL to cancel this redaction.

+

DELETE is the only verb you can use on this URL. GET will always return a 404.

+
+
+ type: + string + + normalEvents, + cardholder + + +
+
+

Whether this redaction is for cardholder events or cardholder information.

+
+
+ when: + string + (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.

+
+
+ before: + string + (date-time) + +
+
+

For event redactions, do not redact any events after this time. No effect on cardholder information redactions.

+

Optional.

+
+
+ status: + string + + pending, + inProgress, + cancelled, + done, + failed + + +
+
+

The status of this redaction.

+
+
+ redactionOperator: + object + +
+
+

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"
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Access group summary: + +

+
+
+
+

The access group search at /api/access_groups returns an array of these, and /api/access_groups/{id} (linked as the href in this object) returns one with more fields.

+
+
+
+
+ id: + string + +
+
+

An alphanumeric identifier for this access group. No API calls use access group IDs.

+
+
+ href: + string + (url) + +
+
+

A link to an + access group detail object for this access group.

+
+
+ name: + string + +
+
+ description: + string + +
+
+ parent: + object + +
+
+

A link to the group's parent, and its name.

+
+
+ division: + object + +
+
+

The division that contains this access group.

+
+
+ cardholders: + object + +
+
+

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.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "id": "352",
+  "href": "https://localhost:8904/api/access_groups/352",
+  "name": "R&D special projects group.",
+  "description": "Deep underground.",
+  "parent": {
+    "href": "https://localhost:8904/api/access_groups/100",
+    "name": "All R&D"
+  },
+  "division": {
+    "id": "2",
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "cardholders": {
+    "href": "https://localhost:8904/api/access_groups/352/cardholders"
+  },
+  "serverDisplayName": "ruatoria.satellite.int"
+}
+
+ + +
+
+
+
+
+

+ Access group detail: + +

+
+
+
+

+ /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'.

+
+
+
+ +
+
+
+
+
+ children: + 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.

+
+
+
+ object + +
+
+
+
+ href: + string + (url) + +
+
+

The href of the child access group.

+
+
+ name: + string + +
+
+

The name of the child access group.

+
+
+
+
+
+
+
+ personalDataDefinitions: + object[] + +
+
+

The names and hrefs of the PDFs that this access group gives to its members.

+
+
+ visitor: + boolean + +
+
+

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.

+
+
+ escortVisitors: + boolean + +
+
+

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.

+
+
+ lockUnlockAccessZones: + boolean + +
+
+

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.

+
+
+ enterDuringLockdown: + boolean + +
+
+

If true, members of this group are not subject to lockdown restrictions when requesting to enter its access zones.

+

New to 8.40.

+
+
+ firstCardUnlock: + boolean + +
+
+

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.

+
+
+ overrideAperioPrivacy: + boolean + +
+
+

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.

+
+
+ aperioOfflineAccess: + boolean + +
+
+

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.

+
+
+ disarmAlarmZones: + boolean + +
+
+

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.

+
+
+ armAlarmZones: + boolean + +
+
+

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.

+
+
+ hvLfFenceZones: + boolean + +
+
+

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.

+
+
+ viewAlarms: + boolean + +
+
+

If true, members of this group can view alarms and inputs on remote arming terminals ("RATs") and HBUS terminals.

+

New to 8.40.

+
+
+ shunt: + boolean + +
+
+

If true, members of this group can shunt (isolate) items using RATs and HBUS terminals.

+

New to 8.40.

+
+
+ lockOutFenceZones: + boolean + +
+
+

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.

+
+
+ cancelFenceZoneLockout: + boolean + +
+
+

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.

+
+
+ ackAll: + boolean + +
+
+

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.

+
+
+ ackBelowHigh: + boolean + +
+
+

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.

+
+
+ selectAlarmZone: + boolean + +
+
+

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.

+
+
+ armWhileAlarm: + boolean + +
+
+

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.

+
+
+ armWhileActiveAlarm: + boolean + +
+
+

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.

+
+
+ isolateAlarmZones: + boolean + +
+
+

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.

+
+
+ access: + object[] + +
+
+

Names and hrefs of the access zones to which this access group gives access, and the schedules that govern it.

+

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.

+
+
+ saltoAccess: + object[] + +
+
+

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.

+
+
+
+ object + +
+
+

Each element in the array contains three blocks: the Salto item type, the item itself, and the controlling schedule.

+
+
+
+
+ saltoItemType: + object + +
+
+

This block tells you whether the Salto item is a Salto door or a Salto 'access zone'.

+
+
+
+
+
+ value: + string + + saltoAccessZone, + saltoDoor + + +
+
+
+
+
+ saltoItem: + object + +
+
+

The name and href of the Salto zone or Salto door to which this access group gives access.

+
+
+
+
+
+
+
+ alarmZones: + object[] + +
+
+

Names and hrefs of the alarm zones to which members of this access group have the 20-odd management privileges listed above.

+

Added in 8.40.

+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "id": "352",
+  "href": "https://localhost:8904/api/access_groups/352",
+  "name": "R&D special projects group.",
+  "description": "Deep underground.",
+  "parent": {
+    "href": "https://localhost:8904/api/access_groups/100",
+    "name": "All R&D"
+  },
+  "division": {
+    "id": "2",
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "cardholders": {
+    "href": "https://localhost:8904/api/access_groups/352/cardholders"
+  },
+  "serverDisplayName": "ruatoria.satellite.int",
+  "children": [
+    {
+      "href": "https://localhost:8904/api/access_groups/5122",
+      "name": "R&D super-special projects"
+    },
+    {
+      "href": "https://localhost:8904/api/access_groups/3420",
+      "name": "R&D social committee"
+    }
+  ],
+  "personalDataDefinitions": [
+    {
+      "name": "email",
+      "href": "https://localhost:8904/api/personal_data_fields/5516"
+    },
+    {
+      "name": "cellphone",
+      "href": "https://localhost:8904/api/personal_data_fields/9998"
+    }
+  ],
+  "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://localhost:8904/api/access_zones/333",
+        "name": "Twilight zone"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/5",
+        "name": "Default Cardholder Access Granted"
+      }
+    },
+    {
+      "accessZone": {
+        "href": "https://localhost:8904/api/access_zones/412",
+        "name": "Server room"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/557",
+        "name": "8am-5pm weekdays"
+      }
+    }
+  ],
+  "saltoAccess": [
+    {
+      "saltoItemType": {
+        "value": "saltoAccessZone"
+      },
+      "saltoItem": {
+        "href": "https://localhost:8904/api/items/570",
+        "name": "Salto BLE CV19"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/5",
+        "name": "Default Cardholder Access Granted"
+      }
+    },
+    {
+      "saltoItemType": {
+        "value": "saltoDoor"
+      },
+      "saltoItem": {
+        "href": "https://localhost:8904/api/items/579",
+        "name": "Salto CU5000"
+      },
+      "schedule": {
+        "href": "https://localhost:8904/api/schedules/557",
+        "name": "8am-5pm weekdays"
+      }
+    }
+  ],
+  "alarmZones": [
+    {
+      "alarmZone": {
+        "href": "https://localhost:8904/api/alarm_zones/328",
+        "name": "Roswell building 2 lobby alarms"
+      }
+    },
+    {
+      "alarmZone": {
+        "href": "https://localhost:8904/api/alarm_zones/10138",
+        "name": "Roswell building 3 lobby alarms"
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Access group membership: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

The name and href of the member cardholder.

+
+
+ from: + string + (time-stamp) + +
+
+ until: + string + (time-stamp) + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/325/access_groups/D714D8A894724F",
+  "cardholder": {
+    "name": "Boothroyd, Algernon",
+    "href": "https://localhost:8904/api/cardholders/325"
+  },
+  "from": "2017-01-01T00:00:00Z",
+  "until": "2017-12-31T11:59:59Z"
+}
+
+ + +
+
+
+
+ +
+

+ Locker bank summary: + +

+
+
+
+

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.

+

The most important field is the href to the detail page, covered next.

+
+
+
+
+ href: + string + (url) + +
+
+

A link to the detail page for this locker bank.

+
+
+ name: + string + +
+
+ shortName: + string + +
+
+

The bank's short name.

+
+
+ description: + string + +
+
+ division: + object + +
+
+

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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Locker bank detail: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A self-reference.

+
+
+ name: + string + +
+
+ shortName: + string + +
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this locker bank.

+
+
+ notes: + string + +
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of + event filters.

+
+
+
+
+
+ lockers: + object[] + +
+
+

An array of locker objects, each containing the details of the locker and the cardholders who can open it.

+
+
+
+ object + +
+
+
+
+ name: + string + +
+
+ shortName: + string + +
+
+

A T20 will display this instead of the long name, if it has both.

+
+
+ description: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ connectedController: + object + +
+
+

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.

+
+
+ assignments: + 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.

+
+
+
+ object + +
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

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.

+
+
+ from: + string + (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.

+
+
+ until: + string + (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.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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"
+        }
+      ]
+    },
+    {
+      "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"
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Locker detail: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A self-reference.

+
+
+ name: + string + +
+
+ shortName: + string + +
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this locker.

+
+
+ notes: + string + +
+
+

The notes field is not in the default result set. You must ask for it using fields.

+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

An alphanumeric identifier, unique to the server. This is the ID to use in the source parameter of + event filters.

+
+
+
+
+
+ assignments: + 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.

+
+
+
+ object + +
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

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.

+
+
+ from: + string + (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.

+
+
+ until: + string + (date-time) + +
+
+

If missing, the locker assignment has no end time, meaning the cardholder can open the locker after the 'from' time.

+
+
+
+
+
+
+
+ commands: + object + +
+
+

Overrideable items return one of these blocks if your operator has the right privilege ('Override - Open Locker' in this case) and the item is fit to be overridden.

+

The only override you can send to a locker is 'open', so that is the only entry this block will contain.

+
+
+
+
+
+ open: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the locker to open its door.

+
+
+
+
+
+
+
+
+
+
+
+
+
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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+

+ Competency PATCH and POST example: object + +

+
+
+
+

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.

+
+
+
+
+ name: + string + +
+
+

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 value for you.

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

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

+
+
+ description: + string + +
+
+

The new item's description.

+
+
+ division: + object + +
+
+

The division to contain this competency.

+
+
+ notes: + string + +
+
+

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

+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "New competency",
+  "shortName": "C4",
+  "description": "Translated automatically.",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "A very long string."
+}
+
+ + +
+
+
+
+ +
+

+ Competency summary: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+ id: + string + +
+
+ name: + string + +
+
+ description: + string + +
+
+ serverDisplayName: + string + +
+
+ notes: + string + +
+
+
+
+
+
+
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": ""
+}
+
+ + +
+
+
+
+
+

+ Competency detail: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+ id: + string + +
+
+ name: + string + +
+
+ description: + string + +
+
+ serverDisplayName: + string + +
+
+ division: + object + +
+
+

The division containing this competency.

+
+
+ notes: + string + +
+
+ shortName: + string + +
+
+ expiryNotify: + boolean + +
+
+

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.

+
+
+ noticePeriod: + object + +
+
+

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.

+
+
+
+
+
+ units: + string + + days, + weeks, + months, + years + + +
+
+ number: + integer + 0 ≤ x ≤ 999 + 0 +
+
+
+
+
+ defaultExpiry: + object + +
+
+

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.

+
+
+
+
+
+ expiryType: + string + + none, + durationdays, + durationweeks, + durationmonths, + durationyears, + date + + + none +
+
+

If 'none', there is no default expiry.

+
+
+ expiryValue: + string or number + +
+
+

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.

+
+
+
+
+
+ defaultAccess: + string + + noAccess, + readOnly, + fullAccess + + + fullAccess +
+
+

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",
+  "division": {
+    "id": "2",
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "",
+  "shortName": "",
+  "expiryNotify": false,
+  "noticePeriod": {
+    "units": "weeks",
+    "number": 2
+  },
+  "defaultExpiry": {
+    "expiryType": "durationmonths",
+    "expiryValue": 6
+  },
+  "defaultAccess": "fullAccess"
+}
+
+ + +
+
+
+
+ +
+

+ Card type: + +

+
+
+
+

This object describes a single Card Type. "Credential type" would be a better name, as it includes mobile credentials.

+
+
+
+
+ href: + string + (url) + +
+
+

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. That contains nothing of use, so there is no point GETting it. Doing so will return you a 404 if you do not have 'View site' or 'Configure site' on the card type's division.

+
+
+ id: + string + +
+
+ name: + string + +
+
+ division: + object + +
+
+

The division that contains this card type. New to 8.50.

+
+
+ notes: + string + +
+
+

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.

+
+
+ facilityCode: + string + +
+
+

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.

+
+
+ availableCardStates: + string[] + +
+
+

All credential types have a set of card states.

+

If you need this, ask for it using the fields parameter.

+
+
+
+ string + + Active, + Disabled (manually), + Lost, + Stolen, + Damaged + + +
+
+
+
+ credentialClass: + string + + piv, + pivi, + card, + mobile, + digitalId, + govPass, + trackingTag, + transact + + +
+
+ minimumNumber: + string + +
+
+

For card types with integer card numbers, this is the minimum.

+
+
+ maximumNumber: + string + +
+
+

For card types with integer card numbers, this is the maximum.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ regex: + string + +
+
+

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

+
+
+ regexDescription: + string + +
+
+

Regular expressions often need explaining.

+
+
+ defaultExpiry: + object + +
+
+

Reserved for internal use.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/card_types/600",
+  "id": "600",
+  "name": "Red DESFire visitor badge",
+  "division": {
+    "id": "2",
+    "href": "https://localhost: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",
+  "defaultExpiry": "Reserved"
+}
+
+ + +
+
+
+
+ +
+

+ Operator group summary: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

A link to an + operator group detail object for this operator group.

+
+
+ name: + string + +
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/operator_groups/523",
+  "name": "Locker admins.",
+  "serverDisplayName": "ruatoria.satellite.int"
+}
+
+ + +
+
+
+
+
+

+ Operator group detail: + +

+
+
+
+

+ /api/operator_groups/{id} returns one of these. In addition to the basic items details such as division and description, it lists the divisions in which it grants privileges to its members.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division that contains this operator group. This has no bearing on the divisions in which this operator group grants privileges; that is divisions.

+
+
+ cardholders: + object + +
+
+

Following this link lists the group's + cardholder members.

+
+
+ divisions: + array + +
+
+

An array containing the divisions in which this operator group grants its privileges.

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

+ Operator group membership: + +

+
+
+
+

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.

+
+
+
+
+ href: + string + (url) + +
+
+

+ DELETE this URL to remove the cardholder from this operator group.

+

DELETE is the only verb you can use on this URL. GET will always 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.

+
+
+ cardholder: + object + +
+
+

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"
+  }
+}
+
+ + +
+
+
+
+ +
+

+ PDF definition: + +

+
+
+
+

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

+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this PDF definition.

+
+
+ type: + string + + string, + image, + strEnum, + numeric, + date, + address, + phone, + email, + mobile + + +
+
+

The type of PDF: string, image, email address, etc.

+
+
+ default: + string + +
+
+

This field will be missing if there is no default value.

+
+
+ required: + boolean + +
+
+

If true, every cardholder with this PDF must have a value for it. No blanks allowed.

+
+
+ unique: + boolean + +
+
+

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.

+
+
+ defaultAccess: + string + + noAccess, + readOnly, + fullAccess + + + fullAccess +
+
+

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.

+
+
+ operatorAccess: + string + + noAccess, + readOnly, + fullAccess + + +
+
+

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.

+
+
+ sortPriority: + integer + +
+
+

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.

+
+
+ accessGroups: + array + +
+
+

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.

+
+
+ regex: + string + +
+
+

This is the regular expression that a PDF value must match before Command Centre will accept it.

+
+
+ regexDescription: + string + +
+
+

Regular expressions often need explaining.

+
+
+ notificationDefault: + boolean + +
+
+

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.

+

New in 8.50.

+
+
+ imageWidth: + integer + +
+
+

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.

+

New in 8.50.

+
+
+ imageHeight: + integer + +
+
+

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.

+

New in 8.50.

+
+
+ imageFormat: + string + + bmp, + jpg, + 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.

+

New in 8.50. Deprecated in 8.70 by contentType, which is more standard.

+
+
+ contentType: + string + + image/bmp, + image/jpeg, + image/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.

+

New in 8.70.

+
+
+ isProfileImage: + boolean + +
+
+

True if and only if this PDF holds images and it is set as a profile image.

+

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"
+    }
+  ],
+  "regex": ".*@.*",
+  "regexDescription": "@ least",
+  "notificationDefault": false,
+  "imageWidth": 600,
+  "imageHeight": 800,
+  "imageFormat": "jpg",
+  "contentType": "image/jpeg",
+  "isProfileImage": false
+}
+
+ + +
+
+
+
+ +
+

+ Reception: + +

+
+
+
+

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

+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

This is the href to use when creating a visit.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ description: + string + +
+
+

Short free-form text. Searches do not return an item's description - ask for it using the fields parameter.

+
+
+ division: + object + +
+
+

The division containing this reception.

+
+
+ defaultVisitorType: + object + +
+
+

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.

+
+
+ notes: + string + +
+
+

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

+
+
+
+

An array of these comes from GET /cardholders/redactions.

+
+
+
+ +
+
+
+
+
+ cardholder: + object + +
+
+

The href of the cardholder whose events or item this redaction should affect.

+

Required.

+
+
+ finished: + string + (date-time) + +
+
+

When the redaction finished.

+

Will be missing from pending redactions.

+
+
+ message: + string + +
+
+

Translated string from the redaction's error code. Will be absent if empty, or if the redaction is pending or complete.

+
+
+ details: + string + +
+
+

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": ""
+}
+
+ + +
+
+
+
+
+

+ Redaction POST Example: + +

+
+
+
+

POST one of these to schedule a redaction.

+
+
+
+
+ cardholder: + object + + +
+
+

The href of the cardholder whose events or item this redaction should affect.

+

Required.

+
+
+ type: + string + + normalEvents, + cardholder + + + +
+
+

Whether this redaction is for events or cardholder information.

+

Required.

+
+
+ when: + string + (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.

+
+
+ before: + string + (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",
+  "when": "2023-01-01T00:00:00Z",
+  "before": "2022-01-01T00:00:00Z"
+}
+
+ + +
+
+
+
+ +
+

+ Role: + +

+
+
+
+

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

+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

This is the string to use when creating a relationship between cardholders using this role.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this role.

+
+
+ id: + string + +
+
+

An alphanumeric identifier, unique to the server. No API calls use role IDs.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Supervisor",
+  "href": "https://localhost:8904/api/roles/1399",
+  "serverDisplayName": "ruatoria.satellite.int",
+  "description": "aka floor manager",
+  "division": {
+    "id": "2",
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "id": "1383"
+}
+
+ + +
+
+
+
+ +
+

+ Visit: + +

+
+
+
+

GET /api/visits returns an array of these, GET /api/visits/{id} returns one, and you can send one in a POST or PATCH to /api/visits.

+
+
+
+
+ name: + string + +
+
+

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!

+
+
+ href: + string + (url) + +
+
+

This is the URL to send a PATCH to when modifying an existing visit. It comes out of a GET or in the Location header of a POST that creates a visit, and the server will ignore it if you send it in the body of a POST or a PATCH.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ description: + string + +
+
+

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.

+
+
+ division: + object + +
+
+

The division containing this visit. This 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.

+

You cannot change a visit's division directly. You can affect it indirectly by changing the visit's reception.

+
+
+ reception: + object + +
+
+

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"

+
+
+ visitorType: + object + +
+
+

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 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'. The 'Modify Access Control' privilege 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.

+
+
+ host: + object + +
+
+

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.

+
+
+ from: + string + (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.

+
+
+ until: + string + (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.

+
+
+ location: + string + +
+
+

Free text. Entirely optional in the POST and PATCH.

+
+
+ visitorAccessGroups: + array + +
+
+

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 access group hrefs. 8.50 does not accept the same kind of visitor access group hrefs here that it sends, so 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 such as 'Modify Access Control' 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'.

+
+
+ visitors: + 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 their state (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 by the server's language. type comes from a fixed enumeration.

+

The invitation string is opaque: please do not interpret it.

+

A visit with no visitors on it 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 hrefs to cardholders. 8.50 does not accept the same kind of visitor hrefs here that it sends, so this is the other reason you cannot copy a visit by POSTing back the JSON from a GET. Don't 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 one or two arrays called add and remove. 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 such as 'Modify Access Control' that allows both viewing the cardholders and changing their access groups memberships, otherwise you will be told 'Access denied when writing to one or more fields'.

+
+
+
+ object + +
+
+
+
+ cardholder: + object + +
+
+

A block containing the name and cardholder href of the visitor.

+
+
+ href: + string + (url) + +
+
+

An href unique to this cardholder on this visit. You can PATCH it to change the visitor's state.

+

New to 8.90.

+
+
+ invitation: + string + +
+
+

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.

+
+
+ status: + object + +
+
+

The cardholder's status on this visit, in human-readable and machine-readable forms.

+

Also new to 8.90.

+
+
+
+
+
+ value: + string + +
+
+

A human-readable version of the visitor's status, varying with the version of Command Centre and the server's locale.

+
+
+ type: + string + + expected, + signingIn, + signedIn, + onSite, + expectedBack, + departed, + cancelled + + +
+
+

The visitor's current state taken from a fixed list of strings. New visitors have a starting state of 'expected'.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Greeter, 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": [
+    {
+      "accessGroup": {
+        "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"
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Visit POST example: + +

+
+
+
+

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.

+
+
+
+
+ name: + string + + +
+
+

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!

+
+
+ description: + string + +
+
+

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.

+
+
+ reception: + object + + +
+
+

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"

+
+
+ visitorType: + object + + +
+
+

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 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'. The 'Modify Access Control' privilege 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.

+
+
+ host: + object + + +
+
+

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.

+
+
+ from: + string + (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.

+
+
+ until: + string + (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.

+
+
+ location: + string + +
+
+

Free text. Entirely optional in the POST and PATCH.

+
+
+ visitorAccessGroups: + array + +
+
+

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 access group hrefs. 8.50 does not accept the same kind of visitor access group hrefs here that it sends, so 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 such as 'Modify Access Control' 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'.

+
+
+ visitors: + 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 their state (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 by the server's language. type comes from a fixed enumeration.

+

The invitation string is opaque: please do not interpret it.

+

A visit with no visitors on it 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 hrefs to cardholders. 8.50 does not accept the same kind of visitor hrefs here that it sends, so this is the other reason you cannot copy a visit by POSTing back the JSON from a GET. Don't 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 one or two arrays called add and remove. 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 such as 'Modify Access Control' that allows both viewing the cardholders and changing their access groups memberships, otherwise you will be told 'Access denied when writing to one or more fields'.

+
+
+
+ object + +
+
+
+
+ cardholder: + object + +
+
+

A block containing the name and cardholder href of the visitor.

+
+
+ href: + string + (url) + +
+
+

An href unique to this cardholder on this visit. You can PATCH it to change the visitor's state.

+

New to 8.90.

+
+
+ invitation: + string + +
+
+

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.

+
+
+ status: + object + +
+
+

The cardholder's status on this visit, in human-readable and machine-readable forms.

+

Also new to 8.90.

+
+
+
+
+
+ value: + string + +
+
+

A human-readable version of the visitor's status, varying with the version of Command Centre and the server's locale.

+
+
+ type: + string + + expected, + signingIn, + signedIn, + onSite, + expectedBack, + departed, + cancelled + + +
+
+

The visitor's current state taken from a fixed list of strings. New visitors have a starting state of 'expected'.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Greeter, 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": [
+    {
+      "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"
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Visit PATCH example: + +

+
+
+
+

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 is that all fields are optional in a PATCH.

+
+
+
+
+ name: + string + +
+
+

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!

+
+
+ description: + string + +
+
+

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.

+
+
+ reception: + object + +
+
+

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"

+
+
+ visitorType: + object + +
+
+

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 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'. The 'Modify Access Control' privilege 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.

+
+
+ host: + object + +
+
+

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.

+
+
+ from: + string + (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.

+
+
+ until: + string + (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.

+
+
+ location: + string + +
+
+

Free text. Entirely optional in the POST and PATCH.

+
+
+ visitorAccessGroups: + array + +
+
+

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 access group hrefs. 8.50 does not accept the same kind of visitor access group hrefs here that it sends, so 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 such as 'Modify Access Control' 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'.

+
+
+ visitors: + 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 their state (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 by the server's language. type comes from a fixed enumeration.

+

The invitation string is opaque: please do not interpret it.

+

A visit with no visitors on it 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 hrefs to cardholders. 8.50 does not accept the same kind of visitor hrefs here that it sends, so this is the other reason you cannot copy a visit by POSTing back the JSON from a GET. Don't 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 one or two arrays called add and remove. 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 such as 'Modify Access Control' that allows both viewing the cardholders and changing their access groups memberships, otherwise you will be told 'Access denied when writing to one or more fields'.

+
+
+
+ object + +
+
+
+
+ cardholder: + object + +
+
+

A block containing the name and cardholder href of the visitor.

+
+
+ href: + string + (url) + +
+
+

An href unique to this cardholder on this visit. You can PATCH it to change the visitor's state.

+

New to 8.90.

+
+
+ invitation: + string + +
+
+

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.

+
+
+ status: + object + +
+
+

The cardholder's status on this visit, in human-readable and machine-readable forms.

+

Also new to 8.90.

+
+
+
+
+
+ value: + string + +
+
+

A human-readable version of the visitor's status, varying with the version of Command Centre and the server's locale.

+
+
+ type: + string + + expected, + signingIn, + signedIn, + onSite, + expectedBack, + departed, + cancelled + + +
+
+

The visitor's current state taken from a fixed list of strings. New visitors have a starting state of 'expected'.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Greeter, Ginger",
+  "description": "Initial scoping",
+  "reception": {
+    "href": "https://localhost:8904/api/receptions/937"
+  },
+  "visitorType": {
+    "accessgroup": {
+      "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/937"
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+

+ Visitor PATCH example: + +

+
+
+
+

This is a sample PATCH body that marks a visitor as signing in.

+
+
+
+
+ status: + object + +
+
+

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.

+
+
+
+
+
+ value: + string + + expected, + signingIn, + signedIn, + onSite, + expectedBack, + departed, + cancelled + + +
+
+

An enum describing the state in which you want your visitor to be.

+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "status": {
+    "value": "signingIn"
+  }
+}
+
+ + +
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/ref/events.html b/ref/events.html new file mode 100644 index 0000000..0f6abf1 --- /dev/null +++ b/ref/events.html @@ -0,0 +1,8335 @@ + + + + + + Command Centre REST API: Alarms and Events | API Reference + + + + + + + +
+ +
+ +
+ +
+
+

Command Centre REST API: Alarms and Events + API Reference +

+
+
+
+

This document describes how you can use the Command Centre REST API to download, monitor, and create events, and monitor and manage alarms.

+

It has companion documents describing the APIs for + Cardholders, + PIV cards, and + non-cardholder items.

+

See the + events section for examples of how to create events, download events, search for events using various criteria, and receive new events as they arrive.

+

See the + alarms section for the methods you can use to download all current alarms, receive notifications as they change state, and manage them. These methods offer similar alarm-management functions to those in the Premier and Command Centre clients.

+

Helper methods allow you to look up Command Centre's internal identifiers for cardholders, divisions, and other items, so that you can use those identifiers in search filters.

+

Licensing

+

All of the GETs in this document and the POSTs that manage alarms require the RESTEvents licence. The POST to create a new event requires RESTCreateEvents.

+

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.

+

Alarms, events, and items API changes on the roadmap

+
    +
  • You will be able to create and delete divisions, and modify some basic fields.
  • +
+

Alarms and events API changes in 9.00

+ +

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

    +
  • +
  • +

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

    +
  • +
+

Alarms and events 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 to find events that are associated with any item.

    +
  • +
+

Alarms and events 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.

    +
  • +
+

Alarms and events 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.

    +
  • +
+

Alarms and events 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).

    +
  • +
+

Alarms and events 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.

    +
  • +
+

Alarms and events 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.

    +
  • +
+

Alarms and events API changes in 8.10

+ +
+
+
+
Request Content-Types: + application/json +
+
Response Content-Types: + application/json +
+
Schemes: + https +
+
Version: + 1.0.0 +
+
+
+
+
+ +

Authentication

+
+
+
+

+ API key + +

+
+

Clients authenticate by including a pre-shared API key in the Authorization header of each request. Command Centre generates an 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.

+

The API key will be in the format XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX and should be in the header following an authorisation method of GGL-API-KEY and a space. Both should be in upper case. For example:

+

Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB

+

Early versions of Command Centre allowed you to omit the 'GGL-API-KEY'. Later versions of CC and all versions of the API gateway will reject you with a 401 if you do that.

+

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.

+

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.

+

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.

+
+
+
+
+
type
+
+
apiKey
+
+
+
+
name
+
+
Authorization
+
+
+
+
in
+
+
header
+
+
+
+
+
+

Documentation suite

+
+
+
+

The API's reference documentation divides into:

+ + + + + + + + + + + + +

+ + + + + +
+ Alarms, events, and non-cardholder items + +

The alarms and events APIs let you download, monitor, and create events, and download, monitor, and manage alarms.

+

The alarms and events documentation also covers API calls that support divisions and items as they relate to events, as well as bulk status monitoring.

+
+ Cardholders and related items + The cardholder parts of the API let you manage your users, their personal data and credentials (cards), and their links to associated items such as access groups, roles, operator groups, competencies, and lockers.
+ Status and overrides + +

These functions let you monitor and override the types of Command Centre items that have their own status, including access zones, alarm zones, doors, fence zones, inputs, outputs, macros, elevator groups, interlock groups, and schedules.

+

Despite its name this section does not cover + mass-monitoring item status.

+ PIV cards + This supplement describes how to work with PIV and PIV-I cards. It is separate from the main cardholder documentation in the interest of brevity.
+
+
+
+

Forward compatibility (HATEOAS)

+
+
+
+

This is a self-referencing REST API that follows the principles of HATEOAS. Other than the initial GET to /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 licensed API calls.

+

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.

+
+
+
+

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.

    +
  • +
+
+
+
+

Field specifiers (in general)

+
+
+
+

You can specify the fields you want in the results of a query. This lets you:

+
    +
  • save calls by adding more fields to search results, which are terse by default
  • +
  • save bandwidth by putting fewer fields on detail pages, which are verbose by default, and
  • +
  • retrieve more data by adding fields to detail pages that are not normally there, such as an item's status, short name, and notes.
  • +
+

You do this using the query parameter fields to the search, detail, and updates pages. It takes a comma-separated list of field names. They are case-sensitive: copy them carefully from this document. The special field name defaults adds in the fields the server would have sent had you not set the fields parameter at all.

+

Example GETs:

+
    +
  • +

    /api/cardholders?name=Smith&fields=defaults,cards,competencies will list all cardholders with 'Smith' in the first or last name, showing the fields you normally get on a search page plus their cards and competencies.

    +
  • +
  • +

    /api/access_groups/123?fields=children,cardholders will show just the child groups of access group 123 and a link to retrieve its cardholders - nothing else.

    +
  • +
  • +

    /api/outputs?fields=name,shortname,id will show the name, short name, and ID of your outputs.

    +
  • +
  • +

    /api/access_zones?fields=name,doors will show the name and attached doors of your access zones.

    +
  • +
  • +

    /api/alarms?fields=time,message,details will return the time, message text, and details text of your alarms. Not only does that save a lot of data by removing default fields, but it adds the details field which does not normally come out of an alarm search.

    +
  • +
+

Each controller has different optional fields - see their sections under Operations.

+
+
+
+

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 8.30 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
+
+
+
+

Alarms

+
+
+

Use these methods to download, monitor, and manage Command Centre alarms.

+

Alarm use cases

+

Downloading and managing unprocessed alarms

+
    +
  1. GET /api
  2. +
  3. Follow the link at features.alarms.alarms + . You will receive up to 100 alarms, each containing links to its management functions.
  4. +
  5. If step 2 returned results and there is a link at next.href, follow it and repeat.
  6. +
+

Staying up to date

+

After getting all the current alarms using the process above, follow the link at updates.href. It will block until there is a new alarm or a change to an existing alarm.

+
+
+
+ + +
+ Alarms + +
+ + +

+ Get current alarms +

+
+
+
+ GET + /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 you may then follow to long-poll for live updates to alarms.

+

The alarm summary only contains unprocessed alarms, but you can still access processed alarms by finding them in the + event summary and following its alarm.href link to the alarm details.

+

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.

+

Do not code this URL into your application. Take it from alarms.alarms.href in the results of GET /api.

+
+
+
+
+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + time, + message, + source, + type, + eventType, + priority, + state, + active, + division, + notePresets, + view, + comment, + acknowledgeWithComment, + acknowledge, + processWithComment, + process, + details, + history, + instruction, + cardholder, + event + + +
+
+
+

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

+

New to 8.40.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Alarm search + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a RESTEvents licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": {
+        "id": "23035",
+        "name": "Forced door"
+      },
+      "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/next?start=92143&pos=61320"
+  },
+  "updates": {
+    "href": "https://localhost:8904/api/alarms/updates?id=92143.1"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Alarms + +
+ + +

+ Get changes to alarms (or wait) +

+
+
+
+ GET + /api/alarms/updates +
+
+
+
+
+
+

Long poll this link 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) the updates array in its response will be empty.

+

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 its load. You will not miss any updates: each response contains everything that happened since the previous call.

+

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

+
+
+
+
+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + time, + message, + source, + type, + eventType, + priority, + state, + active, + division, + notePresets, + view, + comment, + acknowledgeWithComment, + acknowledge, + processWithComment, + process, + details, + history, + instruction, + cardholder, + event + + +
+
+
+

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

+

New to 8.40.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Alarm updates + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a RESTEvents licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": {
+        "id": "23035",
+        "name": "Forced door"
+      },
+      "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Alarms + +
+ + +

+ Get details of an alarm +

+
+
+
+ GET + /api/alarms/{id} +
+
+
+
+
+
+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm. This is not really a parameter you should insert into your request. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + time, + message, + source, + type, + eventType, + priority, + state, + active, + division, + notePresets, + view, + comment, + acknowledgeWithComment, + acknowledge, + processWithComment, + process, + details, + history, + instruction, + cardholder, + event + + +
+
+
+

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

+

New to 8.40.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Alarm detail + +
+ +
+
+

Success

+
+
+
+
+
404 Not Found
+
+
+

The alarm ID is invalid or you do not have privileges for the alarm.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": {
+    "id": "23035",
+    "name": "Forced door"
+  },
+  "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"
+  },
+  "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"
+  },
+  "cardholder": {
+    "href": "https://localhost:8904/api/cardholders/325",
+    "name": "Smith, Jane",
+    "firstName": "Jane",
+    "lastName": "Smith-Jones"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Alarms + +
+ + +

+ Mark an alarm as viewed +

+
+
+
+ POST + /api/alarms/{id}/view +
+
+
+
+
+
+

Mark the alarm as viewed. Follow the view link in the alarm summary to get here.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional comment.

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm. This is not really a parameter you should insert into your request. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "comment": "Alarm was adequately explained."
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

Success

+
+
+
+
+
404 Not Found
+
+
+

Alarm ID is invalid or you do not have privileges for the alarm.

+
+
+
+
+
+
+
+
+ + +
+ Alarms + +
+ + +

+ Add a comment to an alarm +

+
+
+
+ POST + /api/alarms/{id}/comment +
+
+
+
+
+
+

Follow the comment link in the alarm summary to get here.

+
+
+
+
+
+
+
+
+ +
+
+ +

The comment string.

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm. This is not really a parameter you should insert into your request. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "comment": "Alarm was adequately explained."
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

Success

+
+
+
+
+
404 Not Found
+
+
+

Alarm ID is invalid or you do not have privileges for the alarm.

+
+
+
+
+
+
+
+
+ + +
+ Alarms + +
+ + +

+ Mark an alarm as acknowledged +

+
+
+
+ POST + /api/alarms/{id}/acknowledge +
+
+
+
+
+
+

Follow the acknowledgeWithComment or the acknowledge link in the alarm summary to get here.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional comment.

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm. This is not really a parameter you should insert into your request. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "comment": "Alarm was adequately explained."
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

Success

+
+
+
+
+
404 Not Found
+
+
+

Alarm ID is invalid or you do not have privileges for the alarm.

+
+
+
+
+
+
+
+
+ + +
+ Alarms + +
+ + +

+ Mark an alarm as processed +

+
+
+
+ POST + /api/alarms/{id}/process +
+
+
+
+
+
+

Follow the processWithComment, forceProcess, or process link in the alarm to get here.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional comment.

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm. This is not really a parameter you should insert into your request. Instead you should get the URL of this call from other results: the alarm summary or details, in this case.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "comment": "Alarm was adequately explained."
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

Success

+
+
+
+
+
404 Not Found
+
+
+

Alarm ID is invalid or you do not have privileges for the alarm.

+
+
+
+
+
+
+
+

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, and RESTOverrides.

+
+
+
+ + +
+ Divisions + +
+ + +

+ Create a division [coming soon] +

+
+
+
+ POST + /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.

+

/api only shows API features for which you have the necessary licence.

+

Creating a division requires the RESTCardholders licence.

+

It is coming to a future version of Command Centre.

+
+
+
+
+
+
+
+ +
+ +

The only required field here is the new division's parent. All divisions (except the root) must have a parent.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "Long division",
+  "description": "Quatermasters",
+  "notes": "A very long string.",
+  "parent": {
+    "href": "https://localhost:8904/api/divisions/2"
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

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

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new division.

+
+ string + (url) + +
+
+
+
+
+
+
+ + +
+ Divisions + +
+ + +

+ List divisions +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Sets the maximum number of divisions to return per page.

+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + parent, + visitorManagement + + +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Divisions + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a REST licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Divisions + +
+ + +

+ Get details of a division +

+
+
+
+ GET + /api/divisions/{id} +
+
+
+
+
+
+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the division.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + parent, + visitorManagement + + +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Division + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a REST licence.

+
+
+
+
+
404 Not 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).

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+            }
+          }
+        ]
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Divisions + +
+ + +

+ Update a division [coming soon] +

+
+
+
+ PATCH + /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.

+

Patching divisions is coming to a future version of Command Centre.

+
+
+
+
+
+
+
+ +
+ +

There are no mandatory fields.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "Long division",
+  "description": "Quatermasters",
+  "notes": "A very long string.",
+  "parent": {
+    "href": "https://localhost:8904/api/divisions/2"
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

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

+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

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

+
+
+
+
+
409 Conflict
+
+
+

The item is locked for editing by another operator. The body of the response will tell you which operator is holding the lock.

+
+
+
+
+
+
+
+
+ + +
+ Divisions + +
+ + +

+ Remove a division +

+
+
+
+ DELETE + /api/divisions/{id} +
+
+
+
+
+
+

This call removes a division from Command Centre.

+

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

+
+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not Found
+
+
+

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

+
+
+
+
+
+
+
+

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

    +
  • +
  • +

    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. +
  3. 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.
  4. +
  5. 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.
  6. +
  7. If there were results, follow the link at next.href and repeat.
  8. +
+

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. +
  3. 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.
  4. +
  5. Process the events you receive in that call.
  6. +
  7. 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.
  8. +
+

Reporting on events over a period.

+
    +
  1. GET /api
  2. +
  3. 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 &).
  4. +
+

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. +
  3. 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.
  4. +
  5. Process the events you receive in that call. Often there is only one: the first that arrived after you made the call.
  6. +
  7. Sleep to reduce load on the server.
  8. +
  9. 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.
  10. +
+

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

+
    +
  1. GET /api
  2. +
  3. 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.
  4. +
  5. 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.
  6. +
+

Receiving new events as they occur, starting in the past

+
    +
  1. GET /api
  2. +
  3. Follow the link at features.events.events.href + appending the query parameter after=2021-05-08Z (or whatever timestamp is appropriate).
  4. +
  5. Process the events you receive in that call.
  6. +
  7. Sleep to reduce load on the server.
  8. +
  9. 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.
  10. +
+ +
    +
  1. +

    GET /api

    +
  2. +
  3. +

    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.

    +
  4. +
  5. +

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

    +
  6. +
  7. +

    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.

    +
  8. +
+

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,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 request all events, and the server will only send you movements.

+

If your server is running 9.00 or later, the 'View cardholder events' privilege is still an excellent idea 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. Then look at the type field inside the location block:

+
    +
  • +

    If you are simply after a person's location, use the afterLocation field inside the location block. That could be an access zone or division if they moved or attempted to move, or a workstation or alarms terminal if they logged into one of those. The canonicalTypeName field inside the block will tell you what kind of item it is. Future versions of Command Centre or customisations might add more.

    +
  • +
  • +

    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. It will be an access zone or (for departing visitors) a reception. 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 kind of item it is.

    +
  • +
  • +

    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 were. 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 (added in v8.00), you can use + that API to 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. +
  3. POST to the link at features.events.events.href + .
  4. +
+

There are some rules around creating events, so you should first have a careful read of the + POST documentation.

+

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.

    +
  • +
+

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.

    +
  • +
+
+
+
+ + +
+ Events + +
+ + +

+ Add an event +

+
+
+
+ POST + /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. 8.10 ships with 30 external event types you can use, each in its own event type group.

+

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!

+

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. +
  3. 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.
  4. +
  5. 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.
  6. +
+

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

+
+
+
+
+
+
+
+
+
+ + Event POST body + +
+
+
+ +

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

+ +
+
+
+
+
+
+
Request 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"
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

+
+
+
+
+
204 No Content
+
+
+

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

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new event.

+
+ string + (url) + +
+
+
+
+
+
+
+ + +
+ Events + +
+ + +

+ Search events +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
top
+
in query
+
+ integer + 1 ≤ x ≤ 10000 + 1000 +
+
+
+

Sets the maximum number of events to return per page.

+
+
+
+
+
after
+
in query
+
+ string + (date-time) + +
+
+
+

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.

+
+
+
+
+
before
+
in query
+
+ string + (date-time) + +
+
+
+

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.

+
+
+
+
+
source
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
type
+
in query
+
+ string + +
+
+
+

Restricts events to those whose type has this ID. Separate multiple IDs with commas.

+

The results may include other event types if you also use the group parameter, below.

+

Use the + event groups call to see all event types and groups.

+

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.

+
+
+
+
+
group
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
cardholder
+
in query
+
+ string + +
+
+
+

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

+

Example: cardholder=325

+
+
+
+
+
division
+
in query
+
+ string + +
+
+
+

Restricts events to those in this division (including its child divisions). Separate multiple IDs with commas.

+

Example: division=2,101

+

A more secure option is to set the operator's privileges so that it only has access to those divisions.

+
+
+
+
+
relatedItem
+
in query
+
+ string + +
+
+
+

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

+

Example: relatedItem=3,102

+
+
+
+
+
fields
+
in query
+
+ string + + defaults, + details, + cardholder.pdf_*, + href, + id, + serverDisplayName, + time, + message, + occurrences, + priority, + alarm, + operator, + source, + group, + type, + eventType, + division, + cardholder, + entryAccessZone, + exitAccessZone, + door, + accessGroup, + card, + modifiedItem, + lastOccurrenceTime, + previous, + next, + updates, + location + + +
+
+
+

Sets the fields you want in your results.

+

This is the list of valid fields for 8.40, but it is different for older server versions, so check with the topic + on event field specifiers.

+
+
+
+
+
previous
+
in query
+
+ boolean + + false +
+
+
+

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.

+
+
+
+
+
pos
+
in query
+
+ integer + x ≥ 0 +
+
+
+

Restricts events to those with event IDs greater than this parameter (or less than and including, if you set previous=true).

+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Event search + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a RESTEvents licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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/next?previous=True&pos=61320"
+  },
+  "next": {
+    "href": "https://localhost:8904/api/events/next?pos=61320"
+  },
+  "updates": {
+    "href": "https://localhost:8904/api/events/updates?pos=61320"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Events + +
+ + +

+ Get new events (or wait) +

+
+
+
+ GET + /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 events is very different from /api/events:

+
    +
  • +

    If you follow the link at events.updates.href in the results of GET /api, you will receive the first events that arrive after you make the call and that meet your other search criteria.

    +
  • +
  • +

    If you follow the updates link from the results of GET /api/events or GET + /api/events/updates, the link will contain a bookmark in the pos parameter that causes the server to return the first events that arrive or arrived after that bookmark (and that meet your search criteria, of course).

    +
  • +
+

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

+
+
+
+
+
+
+
+
+
top
+
in query
+
+ integer + 1 ≤ x ≤ 10000 +
+
+
+

Sets the maximum number of events to return per page.

+
+
+
+
+
deadline
+
in query
+
+ integer + 1 ≤ x ≤ 86400 +
+
+
+

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.

+
+
+
+
+
after
+
in query
+
+ string + (date-time) + +
+
+
+

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.

+
+
+
+
+
before
+
in query
+
+ string + (date-time) + +
+
+
+

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.

+
+
+
+
+
source
+
in query
+
+ string + +
+
+
+

Restricts events to those whose source item has this ID. Separate multiple IDs with commas. Use /api/items to search Command Centre's items.

+
+
+
+
+
type
+
in query
+
+ string + +
+
+
+

Restricts events to those with this event type ID. Separate multiple IDs with commas.

+

Use /api/events/groups 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.

+
+
+
+
+
group
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
cardholder
+
in query
+
+ string + +
+
+
+

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

+

Example: cardholder=325

+
+
+
+
+
division
+
in query
+
+ string + +
+
+
+

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

+

Example: division=2,101

+
+
+
+
+
fields
+
in query
+
+ string + + defaults, + details, + cardholder.pdf_*, + href, + id, + serverDisplayName, + time, + message, + occurrences, + priority, + alarm, + operator, + source, + group, + type, + eventType, + division, + cardholder, + entryAccessZone, + exitAccessZone, + door, + accessGroup, + card, + modifiedItem, + lastOccurrenceTime, + previous, + next, + updates, + location + + +
+
+
+

Sets the fields you want in your results.

+

This is the list of valid fields for 8.40, but it is different for older server versions, so check with the topic + on event field specifiers.

+
+
+
+
+
pos
+
in query
+
+ integer + x ≥ 0 +
+
+
+

Restricts events to those with event IDs greater than this parameter.

+

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.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Event search + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a RESTEvents licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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/next?previous=True&pos=61320"
+  },
+  "next": {
+    "href": "https://localhost:8904/api/events/next?pos=61320"
+  },
+  "updates": {
+    "href": "https://localhost:8904/api/events/updates?pos=61320"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Events + +
+ + +

+ Get details of an event +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the event.

+
+
+
+
+
fields
+
in query
+
+ string + + defaults, + details, + cardholder.pdf_*, + href, + id, + serverDisplayName, + time, + message, + occurrences, + priority, + alarm, + operator, + source, + group, + type, + eventType, + division, + cardholder, + entryAccessZone, + exitAccessZone, + door, + accessGroup, + card, + modifiedItem, + lastOccurrenceTime, + previous, + next, + updates, + location + + +
+
+
+

Sets the fields you want in your results.

+

This is the list of valid fields for 8.40, but it is different for older server versions, so check with the topic + on event field specifiers.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Event detail + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a RESTEvents licence.

+
+
+
+
+
404 Not Found
+
+
+

That is not the URL of an event, or it is but you do not have privileges to read events in its division.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Events + +
+ + +

+ List event types +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Event groups + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTEvents or RESTCreateEvents licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+

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

    + POST to create a subscription. Your program should get the URL from items.updates.href in the results of GET /api.

    +
  4. +
  5. +

    Take the current state of your items from the results of that call. If that's all you need, terrific.

    +
  6. +
  7. +

    But if you want to monitor their state, + GET the next.href link that came in the results of the POST. 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.

    +
  8. +
  9. +

    Loop.

    +
  10. +
+

Licensing

+

Every REST licence enables the items controller: RESTEvents, RESTCreateEvents, RESTCardholders, RESTStatus, and RESTOverrides.

+
+
+
+ + +
+ Items + +
+ + +

+ Search items +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string + +
+
+
+

Only returns items that are in these divisions or their subdivisions. Use the divisions' short alphanumeric IDs, separateed by commas.

+
+
+
+
+
type
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 + 100 +
+
+
+

Sets the maximum number of items to return per page.

+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + type, + division, + serverDisplayName, + notes + + +
+
+
+

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.

+

Treat the string matches as case-sensitive.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Item search + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a REST licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Items + +
+ + +

+ Get details of an item +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the item.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + type, + division, + serverDisplayName, + notes + + +
+
+
+

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.

+

Treat the string matches as case-sensitive.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Item detail + +
+ +
+
+

Success

+
+
+
+
+
404 Not Found
+
+
+

That is not the href of an item. At least not one that your operator has the privilege to view.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "id": "325",
+  "name": "Brick, Eva",
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Items + +
+ + +

+ List item types +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Item types + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a REST licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "itemTypes": [
+    {
+      "id": "1",
+      "name": "Cardholder",
+      "canonicalTypeName": "cardholder"
+    },
+    {
+      "id": "2",
+      "name": "Access Group",
+      "canonicalTypeName": "accessgroup"
+    }
+  ]
+}
+
+ + +
+
+
+
+
+ + +
+ Items + +
+ + +

+ Subscribe to status updates +

+
+
+
+ POST + /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.

+

Your operator must have view privileges on every item in the subscription.

+

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.

+
+
+
+
+
+
+
+ +
+ +

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.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "itemIds": [
+    "508",
+    "526"
+  ]
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+ + Item update + +
+ +
+
+

Success

+
+
+
+
+
400 Bad Request
+
+
+

The server could not parse the request body. Check your JSON.

+
+
+
+
+
401 Unauthorized
+
+
+

The operator does not have privilege to view the monitored items. This will change to 403 in a future version of Command Centre.

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTStatus licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": [
+    {
+      "id": "508",
+      "status": "Controller offline. 62 message(s) pending.",
+      "statusText": "Controller offline.\r\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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Items + +
+ + +

+ Retrieve status updates +

+
+
+
+ GET + /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 too much time has passed since the server sent the link, or the server has restarted. In either case the server 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. +
  3. Process the statuses in the results, if there are any.
  4. +
  5. Wait a second or two to avoid tight loops.
  6. +
  7. GET the link from the results. It may take up to a minute to respond.
  8. +
  9. If 404, go to 1.
  10. +
  11. Go to 2.
  12. +
+

...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 data you have seen already (when an override is sent to an item, or some aspect of an item changes that your operator does not have the privilege to view, for example). These two behaviours are not harmful, but also not particularly helpful, so future versions may differ.

+
+
+
+
+
+
+
+
+
bookmark
+ +
in query
+
+ string + +
+
+
+

Identifies your subscription and your position in the change list. You do not need to set this parameter: it will be in the link that the server sends back to you.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Item update + +
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a REST licence.

+
+
+
+
+
404 Not Found
+
+
+

The subscription does not exist, which probably means you waited too long between calls.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": [
+    {
+      "id": "508",
+      "status": "Controller offline. 62 message(s) pending.",
+      "statusText": "Controller offline.\r\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"
+  }
+}
+
+ + +
+
+
+
+

Schema Definitions

+ +
+

+ Alarm updates: object + +

+
+
+
+
+
+ updates: + + + Alarm summary + + + +
+
+

An array of summaries of alarms created or modified since the previous call.

+
+
+
+ + + Alarm summary + + + +
+
+
+
+ next: + object + +
+
+

Follow this link to perform another long poll.

+
+
+
+
+
+ href: + string + +
+
+
+
+
+
+
+
+
+
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": {
+        "id": "23035",
+        "name": "Forced door"
+      },
+      "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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Alarm summary: object + +

+
+
+
+

/api/alarms returns an array of these, and /api/alarms/{id} returns one with more fields.

+
+
+
+
+ href: + string + (url) + +
+
+

A link to the details of this alarm.

+
+
+ id: + string + +
+
+

An alphanumeric identifier for this alarm, unique to the server.

+
+
+ time: + string + +
+
+

The time the alarm occurred.

+
+
+ message: + string + +
+
+

The alarm's message.

+
+
+ source: + object + +
+
+

The ID and href are new to 8.10.

+
+
+
+
+
+ id: + string + +
+
+

The alphanumeric ID of the alarm's source item.

+
+
+ name: + string + +
+
+

The name of the alarm's source item at the time of the event.

+
+
+ href: + string + (url) + +
+
+

Link to the source item.

+
+
+
+
+
+ type: + string + +
+
+

The name of this alarm's event type.

+
+
+ eventType: + object + +
+
+

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.

+
+
+
+
+
+ id: + string + +
+
+

The alphanumeric ID of the event type. You can use this ID in the type parameter when searching for events with + /events.

+
+
+ name: + string + +
+
+

The name of the event type.

+
+
+
+
+
+ priority: + integer + 0 ≤ x ≤ 9 +
+
+

Numeric priority. 9 is critical and 0 is not an event.

+
+
+ state: + string + + unacknowledged, + acknowledged, + processed + + +
+
+

The state of the alarm. Alarms start at "unacknowledged".

+
+
+ active: + boolean + +
+
+

Clients cannot process active alarms.

+
+
+ division: + object + +
+
+

The division entity representing the division of the alarm. GET the href for full details.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ event: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

Link to the event page for this alarm. GET it for the fields found on an event that are not on an alarm.

+
+
+
+
+
+ notePresets: + 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.

+
+
+
+ string + +
+
+
+
+ view: + object + +
+
+

POST an alarm update request JSON object to the href to indicate the operator has viewed the alarm.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ comment: + object + +
+
+

POST an alarm update request JSON object to the href to place an arbitrary alarm note against the alarm.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ acknowledge: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ acknowledgeWithComment: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ process: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ processWithComment: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ forceProcess: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
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": {
+    "id": "23035",
+    "name": "Forced door"
+  },
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Alarm detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ details: + string + +
+
+

The full alarm details text. This may be up to 2048 UTF-8 characters, each of which could (theoretically) be four bytes long.

+
+
+ history: + + + Alarm history entry + + + +
+
+

An array of alarm history entries. Missing if there is no history.

+
+
+
+ + + Alarm history entry + + + +
+
+
+
+ instruction: + object + +
+
+

GET this link to retrieve the alarm instruction body inside an HTML document. Missing if there is no alarm instruction for this alarm.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ cardholder: + object + +
+
+

The cardholder entity associated with this alarm. GET the href for full details. Missing if this alarm does not have an associated cardholder.

+
+
+
+
+
+ href: + string + (url) + +
+
+ name: + string + +
+
+

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.

+
+
+ firstName: + string + +
+
+

The current value of the firstName field of this cardholder. Added in 8.20.

+
+
+ lastName: + string + +
+
+

The current value of the lastName field of this cardholder. Added in 8.20.

+
+
+
+
+
+
+
+
+
+
+
+
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": {
+    "id": "23035",
+    "name": "Forced door"
+  },
+  "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"
+  },
+  "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"
+  },
+  "cardholder": {
+    "href": "https://localhost:8904/api/cardholders/325",
+    "name": "Smith, Jane",
+    "firstName": "Jane",
+    "lastName": "Smith-Jones"
+  }
+}
+
+ + +
+
+
+
+
+

+ Alarm history entry: object + +

+
+
+
+
+
+ time: + string + +
+
+

The time the history entry was added.

+
+
+ action: + string + + legacy, + comment, + acknowledge, + process, + acknowledgeActive, + escalated, + viewed + + +
+
+

The type of the history entry.

+
+
+ comment: + string + +
+
+

The added comment, or a textual description of some occurrence related to the alarm.

+
+
+ operator: + object + +
+
+

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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Alarm update request: object + +

+
+
+
+
+
+ comment: + string + +
+
+

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

+
+
+
+
+
+
+
Example
+ + + +
{
+  "comment": "Alarm was adequately explained."
+}
+
+ + +
+
+
+
+ +
+

+ Event summary: 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.

+
+
+
+
+ href: + string + (url) + +
+
+

A link to this event's details.

+
+
+ id: + string + +
+
+

An alphanumeric identifier for this event, unique to the server.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ time: + string + (date-time) + +
+
+

The time the event occurred.

+
+
+ message: + string + +
+
+

The event's message.

+
+
+ occurrences: + integer + x ≥ 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.

+
+
+ priority: + integer + 0 ≤ x ≤ 9 +
+
+

Numeric priority. 9 is critical and 0 is not an event.

+
+
+ alarm: + object + +
+
+

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.

+
+
+
+
+
+ state: + string + + unacknowledged, + acknowledged, + processed + + +
+
+

Alarms start unacknowledged. Acknowledging or processing them changes that.

+
+
+ href: + string + (url) + +
+
+

Link to the alarm entity corresponding to the event. GET the href for full alarm details.

+
+
+
+
+
+ operator: + object + +
+
+

The href and name of the operator 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).

+
+
+
+
+
+ href: + string + (url) + +
+
+

Link to the operator who caused this event by editing an item. New in 8.00.

+
+
+ name: + string + +
+
+

The name the operator held at the time. New in 8.40.

+
+
+
+
+
+ source: + object + +
+
+

ID and name of the source of the event, as recorded at the time of the event.

+
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+

This could be different from the current name of the source item.

+
+
+ href: + string + (url) + +
+
+

Link to the source item. New in 8.00.

+
+
+
+
+
+ group: + object + +
+
+

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.

+
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+

The name of the event group.

+
+
+
+
+
+ type: + object + +
+
+

ID and name of the event's type. There is a long list of them at + /events/groups.

+
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+

The name of the event type.

+
+
+
+
+
+ eventType: + object + +
+
+

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.

+
+
+
+
+
+ id: + string + +
+
+

The alphanumeric ID of the event type. You can use this ID in the type parameter when searching for events with + /events.

+
+
+ name: + string + +
+
+

The name of the event type.

+
+
+
+
+
+ division: + object + +
+
+

ID, name, and href of the event's division (which is the division of the event's source item, for most event types).

+
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ href: + string + (url) + +
+
+

The link to the division item.

+
+
+ name: + string + +
+
+

The division's name. Added in 8.40.

+
+
+
+
+
+ cardholder: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

Link to the cardholder entity representing the cardholder of the event. GET the href for full details.

+
+
+ id: + string + +
+
+

The alphanumeric ID of the cardholder associated with this event.

+
+
+ name: + string + +
+
+

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.

+
+
+ firstName: + string + +
+
+

The current value of the firstName field of this cardholder. Added in 8.20.

+
+
+ lastName: + string + +
+
+

The current value of the lastName field of this cardholder. Added in 8.20.

+
+
+
+
+
+ entryAccessZone: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+ name: + string + +
+
+

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.

+
+
+ id: + string + +
+
+

Deprecated.

+
+
+
+
+
+ exitAccessZone: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+ name: + string + +
+
+

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.

+
+
+ id: + string + +
+
+

Deprecated.

+
+
+
+
+
+ door: + object + +
+
+

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. New in 8.10.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+ name: + string + +
+
+

The name the door had when the event occurred.

+
+
+
+
+
+ accessGroup: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+

Link to the access group related to this event.

+
+
+
+
+
+ card: + object + +
+
+

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.

+
+
+
+
+
+ facilityCode: + string + +
+
+

The card's facility code at the time of the event expressed as one letter followed by up to five digits.

+
+
+ number: + string + +
+
+

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.

+
+
+ issueLevel: + integer + +
+
+

The issue level of the card at the time.

+
+
+
+
+
+ modifiedItem: + object + +
+
+

The href and type of the item that an operator created, changed, or deleted, for those kinds of events.

+

New in 8.40.

+
+
+
+
+
+ href: + string + (url) + +
+
+

Link to the item that this event modified. Watch for 404s: this link will be here even for deleted items.

+
+
+ type: + object + +
+
+
+
+
+ id: + string + +
+
+

A short alphanum identifying the item's type.

+
+
+ name: + string + +
+
+

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.

+
+
+
+
+
+
+
+
+ next: + object + +
+
+

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.

+

The next link on the last event in a result set will be the same as the next link outside the results.

+

New to 8.70.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ previous: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ updates: + object + +
+
+

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.

+

The updates link on the last event in a result set will be the same as the updates link outside the results, since they both indicate the event that will be at the top of the next result set.

+

New to 8.70.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Event detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ lastOccurrenceTime: + string + (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.

+
+
+ details: + string + +
+
+

The full alarm details text. This may be up to 2048 UTF-8 characters, each of which could (theoretically) be four bytes long.

+
+
+ location: + object + +
+
+

This block gathers together information that is contained 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 these three fields:

+
    +
  • The cardholder whose location the event contains,
  • +
  • the access zone that they started in before the event, and
  • +
  • the item that indicates their location after the event.
  • +
+

The location block will only be in a result if you ask for it using the fields query parameter.

+
+
+
+
+
+ type: + string + + moved, + observed, + denied + + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+

The cardholder's name at the time of the event.

+
+
+ href: + string + (url) + +
+
+

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.

+
+
+
+
+
+ beforeLocation: + object + +
+
+

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.

+
+
+ afterLocation: + object + +
+
+

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
+    }
+  }
+}
+
+ + +
+
+
+
+
+

+ Event POST body: + +

+
+
+
+
+
+ type: + object + + +
+
+

This is a mandatory field in an event. Without it, the POST will fail.

+

It is mandatory because the server cannot assume a reasonable default. It can for the others.

+

If your server is running 8.90 or later, eventType is a synonym. If you send both a type block and an eventType block it will use eventType.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+
+
+
+ eventType: + object + + +
+
+

Synonym for type, introduced in 8.90.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ source: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

Get the href using the API controller for that item type (such as + doors) or from /items.

+
+
+
+
+
+ priority: + integer + 1 ≤ x ≤ 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.

+
+
+ time: + string + (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").

+
+
+ message: + string + +
+
+

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.

+
+
+ details: + string + +
+
+

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.

+
+
+ cardholder: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+
+
+
+ operator: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+
+
+
+ entryAccessZone: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+
+
+
+ accessGroup: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+
+
+
+ lockerBank: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

This can be the href of any locker bank to which your operator has view access. Get the href using the + locker banks or + items controller.

+
+
+
+
+
+ locker: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+
+
+
+ door: + object + +
+
+

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

+
+
+
+
+
+ href: + string + (url) + +
+
+

This can be the href of any door your operator can see. Get the href using the + doors or + items controller.

+
+
+
+
+
+
+
+
+
+
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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Event groups: object + +

+
+
+
+

Calls to /api/events/groups/ return this object, which is a named array of groups of event types.

+
+
+
+
+ eventGroups: + object[] + +
+
+

An array of event group objects. There will be about 150. Most groups contain fewer than 100 event types; one contains around 200.

+
+
+
+ object + +
+
+
+
+ id: + string + +
+
+

The alphanumeric ID of the event group. Use this ID in the group filter when requesting events.

+
+
+ name: + string + +
+
+

The name of the event group.

+
+
+ eventTypes: + object[] + +
+
+

An array of all the event types in the group.

+
+
+
+ object + +
+
+
+
+ id: + string + +
+
+

The alphanumeric ID of the event type. Use this ID in the type filter when requesting events.

+
+
+ name: + string + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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"
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Divisions: object + +

+
+
+
+

Calls inside /api/divisions/ return this object, which is simply a named array of objects each containing some information about a division.

+
+
+
+
+ results: + + + Division + + + +
+
+

An array of division objects.

+
+
+
+ + + Division + + + +
+
+
+
+ next: + object + +
+
+

A site generally does not have too many divisions. You should be able to collect them all in one request by using the top parameter. If not, follow this link to get the next page of divisions.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
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.

+
+
+
+
+ href: + string + (url) + +
+
+

A self reference.

+
+
+ id: + string + +
+
+

The alphanumeric ID of the division. Use this ID in the division filter when requesting events.

+
+
+ name: + string + +
+
+

The division's name.

+
+
+ description: + string + +
+
+

The division's description. New in 8.50. Not sent by default; ask for it with fields.

+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ parent: + object + +
+
+

An object containing an href to the division entity representing the current division's parent. GET the href for full details.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ visitorManagement: + object + +
+
+

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

+
+
+
+
+
+ active: + boolean + +
+
+

If present and true, this division has its own visitor management configuration. Otherwise it uses its parent's. That is also indicated by the presence or absence of the visitorTypes block.

+
+
+ visitortypes: + 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).

+
+
+
+ object + +
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ accessGroup: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+
+
+
+ hostAccessGroups: + 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.

+
+
+
+ object + +
+
+
+
+ accessGroup: + object + +
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
+ visitorAccessGroups: + 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.

+
+
+
+ object + +
+
+
+
+ accessGroup: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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"
+            }
+          }
+        ]
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+

+ Division PATCH and POST example: 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.

+
+
+
+
+ name: + string + +
+
+

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.

+
+
+ description: + string + +
+
+

The division's description.

+
+
+ notes: + string + +
+
+

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

+
+
+ parent: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Long division",
+  "description": "Quatermasters",
+  "notes": "A very long string.",
+  "parent": {
+    "href": "https://localhost:8904/api/divisions/2"
+  }
+}
+
+ + +
+
+
+
+ +
+

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

+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+ type: + object + +
+
+

The type object contains the ID and name of the item's type.

+
+
+
+
+
+ id: + string + +
+
+

Use this ID in the type filter when requesting items if you wish to restrict the results to items of a certain type.

+
+
+ name: + string + +
+
+

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.

+
+
+ canonicalTypeName: + string + +
+
+

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.

+
+
+
+
+
+ serverDisplayName: + string + +
+
+

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.

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ href: + string + +
+
+

Reserved for internal use. Its value will change in a future version.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "id": "325",
+  "name": "Brick, Eva",
+  "type": {
+    "id": "1",
+    "name": "Cardholder",
+    "canonicalTypeName": "cardholder"
+  },
+  "serverDisplayName": "ruatoria.satellite.int",
+  "notes": "Multi-line text...",
+  "href": "string"
+}
+
+ + +
+
+
+
+
+

+ Item detail: 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.

+
+
+
+ +
+
+
+
+
+ division: + object + +
+
+
+
+
+ id: + string + +
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "id": "325",
+  "name": "Brick, Eva",
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Item types: object + +

+
+
+
+

Calls to /api/items/types return this object, an array of item types.

+
+
+
+
+ itemTypes: + object[] + +
+
+

A list of item types.

+
+
+
+ object + +
+
+
+
+ id: + string + +
+
+

The alphanumeric ID of the item type. Use this ID in the type filter when requesting items.

+
+
+ name: + string + +
+
+

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.

+
+
+ canonicalTypeName: + string + +
+
+

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

+ Item update: 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.

+
+
+
+
+ updates: + 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). 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.

+
+
+
+ object + +
+
+
+
+ id: + string + +
+
+

The item's ID, from the list you sent in the POST that created this subscription.

+
+
+ statusFlags: + array + +
+
+

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.

+

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.

+
+
+ statusText: + string + +
+
+

The state of the item in a multi-line string taken from the server's language pack.

+
+
+ status: + string + +
+
+

A one-line version of the status, with details removed if necessary to keep it short.

+
+
+
+
+
+
+
+ next: + object + +
+
+

A link to GET more status updates. Do not wait longer than thirty seconds before using this link 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.\r\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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Item update subscription: object + +

+
+
+
+

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.

+
+
+
+
+ itemIds: + string[] + + +
+
+
+ string + +
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "itemIds": [
+    "506"
+  ]
+}
+
+ + +
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/ref/images/gallagher.png b/ref/images/gallagher.png new file mode 100644 index 0000000..6ec4aa6 Binary files /dev/null and b/ref/images/gallagher.png differ diff --git a/ref/index.html b/ref/index.html new file mode 100644 index 0000000..322a20f --- /dev/null +++ b/ref/index.html @@ -0,0 +1,109 @@ + + + + + + Command Centre REST API | API Reference + + + + + + + +
+ +
+ +
+ +
+
+

Command Centre REST API + API Reference +

+
+
+
+

Welcome to the developer documentation for the Command Centre REST interface.

+

The files linked from here show you how you can manage Command Centre using HTTP requests. Each file contains an introduction to its particular area of the API, instructions on authenticating to the server, and tips on how to accomplish common tasks, as well as the usual reference information for paths and data.

+

They do not show how to set up the Command Centre server to answer your REST calls. To do that, search the Configuration Client's online help for 'REST'.

+

You could also look at the sample code shipped with Command Centre, on the install media alongside this documentation in the 'Utilities/REST API' folder. There are several Visual Studio projects that compile into .NET applications you could observe making HTTP requests against your own server (in a development environment, of course).

+
+
+
+
Version: + 1.0.0 +
+
+
+
+
+

Documentation suite

+
+
+
+

The API's reference documentation divides into:

+ + + + + + + + + + + + +

+ + + + + +
+ Alarms, events, and non-cardholder items + +

The alarms and events APIs let you download, monitor, and create events, and download, monitor, and manage alarms.

+

The alarms and events documentation also covers API calls that support divisions and items as they relate to events, as well as bulk status monitoring.

+
+ Cardholders and related items + The cardholder parts of the API let you manage your users, their personal data and credentials (cards), and their links to associated items such as access groups, roles, operator groups, competencies, and lockers.
+ Status and overrides + +

These functions let you monitor and override the types of Command Centre items that have their own status, including access zones, alarm zones, doors, fence zones, inputs, outputs, macros, elevator groups, interlock groups, and schedules.

+

Despite its name this section does not cover + mass-monitoring item status.

+ PIV cards + This supplement describes how to work with PIV and PIV-I cards. It is separate from the main cardholder documentation in the interest of brevity.
+
+
+
+

Paths

+ +
+
+
+ + \ No newline at end of file diff --git a/ref/javascripts/spectacle.min.js b/ref/javascripts/spectacle.min.js new file mode 100644 index 0000000..9169353 --- /dev/null +++ b/ref/javascripts/spectacle.min.js @@ -0,0 +1 @@ +function Traverse(t,e){this.$element=t,this.options=$.extend({},Traverse.defaults,this.$element.data(),e),this._init()}$(function(){var s,a,n,o,t=$("#sidebar");t.length&&($("#docs"),new Traverse(s=t.find("nav"),{threshold:10,barOffset:t.position().top}),s.on("update.traverse",function(t,e){s.find("section").removeClass("expand");var i=e.parents("section:first");i.length&&i.addClass("expand")}),a=$(".drawer-layout"),n=a.find(".drawer"),o=function(){return n.removeClass("slide-right slide-left"),n.find(".drawer-overlay").remove(),a.removeClass("drawer-open drawer-slide-left-large drawer-slide-right-large"),!1},a.find("[data-drawer-slide]").click(function(t){var e=$(this).data("drawer-slide");a.addClass("drawer-open"),n.addClass("slide-"+e);var i=$('');return n.append(i),i.click(o),!1}),a.find("[data-drawer-close]").click(o))}),Traverse.defaults={animationDuration:500,animationEasing:"linear",threshold:50,activeClass:"active",deepLinking:!1,barOffset:0},Traverse.prototype._init=function(){var t=this.$element[0].id;this.$targets=$("[data-traverse-target]"),this.$links=this.$element.find("a"),this.$element.attr({"data-resize":t,"data-scroll":t,id:t}),this.$active=$(),this.scrollPos=parseInt(window.pageYOffset,10),this._events()},Traverse.prototype.calcPoints=function(){var i=this,t=document.body,e=document.documentElement;this.points=[],this.winHeight=Math.round(Math.max(window.innerHeight,e.clientHeight)),this.docHeight=Math.round(Math.max(t.scrollHeight,t.offsetHeight,e.clientHeight,e.scrollHeight,e.offsetHeight)),this.$targets.each(function(){var t=$(this),e=t.offset().top;t.targetPoint=e,i.points.push(e)})},Traverse.prototype._events=function(){var s=this,a=$("html, body"),n={duration:s.options.animationDuration,easing:s.options.animationEasing};$(window).one("load",function(){s.calcPoints(),s._updateActive(),$(this).resize(function(t){s.reflow()}).scroll(function(t){s._updateActive()})}),this.$element.on("click",'a[href^="#"]',function(t){t.preventDefault();var e=this.getAttribute("href").replace(/\./g,"\\."),i=$(e).offset().top-s.options.barOffset;a.stop(!0).animate({scrollTop:i},n)})},Traverse.prototype.reflow=function(){this.calcPoints(),this._updateActive()},Traverse.prototype._updateActive=function(){var i,s,t,e,a=parseInt(window.pageYOffset,10);e=a+this.winHeight===this.docHeight?this.points.length-1:a + + + + + Command Centre REST API: PIV card supplement | API Reference + + + + + + + +
+ +
+ +
+ +
+
+

Command Centre REST API: PIV card supplement + API Reference +

+
+
+
+

This document is a supplement to the + Cardholder API documentation ('Cardholder' is the Command Centre term for a user). That documentation describes how to add a card to an existing cardholder in a + PATCH, and how to create a cardholder plus cards in a + POST, but in the interests of brevity its examples do not cover PIV.

+

The Schema Definitions section gives you the details of the PIV part of a Command Centre card, with examples of what you would submit to + create one 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 sample + PATCH and + 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. +
  3. If running 8.00 or earlier, follow the link at features.cardTypes.cardTypes.href (which will be to /api/card_types), or
  4. +
  5. 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).
  6. +
  7. Iterate through the array to find the element with credentialClass: piv, and
  8. +
  9. note its href.
  10. +
+

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

+

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.
  • +
+
+
+
+
Request Content-Types: + application/json +
+
Response Content-Types: + application/json +
+
Schemes: + https +
+
Version: + 8.40.2 +
+
+
+
+
+ +

Authentication

+
+
+
+

+ API key + +

+
+

Clients authenticate by including a pre-shared API key in the Authorization header of each request. Command Centre generates an 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.

+

The API key will be in the format XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX and should be in the header following an authorisation method of GGL-API-KEY and a space. Both should be in upper case. For example:

+

Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB

+

Early versions of Command Centre allowed you to omit the 'GGL-API-KEY'. Later versions of CC and all versions of the API gateway will reject you with a 401 if you do that.

+

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.

+

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.

+

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.

+
+
+
+
+
type
+
+
apiKey
+
+
+
+
name
+
+
Authorization
+
+
+
+
in
+
+
header
+
+
+
+
x-external
+
+
eventsApi.yaml#/securityDefinitions/API key
+
+
+
+
+
+

Documentation suite

+
+
+
+

The API's reference documentation divides into:

+ + + + + + + + + + + + +

+ + + + + +
+ Alarms, events, and non-cardholder items + +

The alarms and events APIs let you download, monitor, and create events, and download, monitor, and manage alarms.

+

The alarms and events documentation also covers API calls that support divisions and items as they relate to events, as well as bulk status monitoring.

+
+ Cardholders and related items + The cardholder parts of the API let you manage your users, their personal data and credentials (cards), and their links to associated items such as access groups, roles, operator groups, competencies, and lockers.
+ Status and overrides + +

These functions let you monitor and override the types of Command Centre items that have their own status, including access zones, alarm zones, doors, fence zones, inputs, outputs, macros, elevator groups, interlock groups, and schedules.

+

Despite its name this section does not cover + mass-monitoring item status.

+ PIV cards + This supplement describes how to work with PIV and PIV-I cards. It is separate from the main cardholder documentation in the interest of brevity.
+
+
+
+

Forward compatibility (HATEOAS)

+
+
+
+

This is a self-referencing REST API that follows the principles of HATEOAS. Other than the initial GET to /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 licensed API calls.

+

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.

+
+
+
+

Paths

+ +
+

+ GET /api +

+
+
+
+ GET + /api +
+
+
+
+
+
+

This is the first call your application should make. It contains the URLs of every other call. The one you need for creating PIV cards is an href in features.cardTypes.cardTypes.

+
+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

Success

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have a REST licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "version": "8.10.0.0",
+  "features": {
+    "cardTypes": {
+      "cardTypes": {
+        "href": "https://localhost:8904/api/card_types"
+      },
+      "assign": {
+        "href": "https://localhost:8904/api/card_types/assign"
+      }
+    },
+    "cardHolders": {
+      "cardHolders": {
+        "href": "https://localhost:8904/api/cardholders"
+      }
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Create a cardholder +

+
+
+
+ POST + /api/cardholders +
+
+
+
+
+
+

Creates a new cardholder, including assigned cards.

+

Part of what you submit is an array called cards, each element of which is a card or credential you want assigned to your new cardholder. This example shows an array containing one PIV card.

+

See the + PIV card create schema definition for the PIV-specific fields. The fields that are not specific to PIV and PIV-I cards are fully documented + in the cardholder API. One, division, is mandatory, and you must also supply either a first or last name for your new cardholder.

+
+
+
+
+
+
+
+
+
+ division: + object + +
+
+

Mandatory when creating any cardholder.

+
+
+ firstName: + string + +
+
+

Optional. See + the cardholder API documentation.

+
+
+ cards: + + + PIV card create example + + + +
+
+
+ + + PIV card create example + + + +
+
+
+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "firstName": "There will be more fields like this",
+  "cards": [
+    {
+      "number": "47000256001337111234567890199991",
+      "status": {
+        "value": "active"
+      },
+      "type": {
+        "href": "https://localhost:8904/api/card_types/244"
+      },
+      "pivData": {
+        "chuid": {
+          "hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
+          "fascn": "47000256001337111234567890199991",
+          "orgIdentifier": "",
+          "duns": ""
+        },
+        "pivStatus": {
+          "type": "Normal"
+        },
+        "contentSigningCert": "MIIE[...]Kltk=",
+        "cardAuthenticationCert": "MIIE[...]e5mE=",
+        "pivAuthenticationCert": "MIIE[...]wkrp",
+        "fingerprints": "N7[...]Shpd="
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new cardholder.

+
+ string + (url) + +
+
+
+
+
+
+ +
+

+ Update a cardholder +

+
+
+
+ PATCH + /api/cardholders/{id} +
+
+
+
+
+
+

Updates an existing cardholder, including adding, removing, and modifying cards.

+
+
+
+
+
+
+

Part of what you submit is an object called cards, described below.

+

The other fields you can pass in this call, such as personal information, access rights, and card data such as issue levels, status, and validity to/from dates, are fully documented in the + main cardholder API. This example shows how to assign a PIV card to a cardholder, and how to change the two status fields of an existing PIV card.

+
+
+
+ firstName: + string + +
+
+

See + the cardholder API documentation. There is a lot you can do here.

+
+
+ cards: + object + +
+
+

cards can contain three arrays called add, update, and delete.

+

An element in the add array is the same as you would supply to a + POST when creating a cardholder with cards. There is an example here. See the + PIV card update schema definition for the PIV-specific field definitions.

+

An element in the update array is much smaller, because the only PIV data you can change is the PIV status. This example changes the PIV status of a card card to 'notChecked', which would normally activate it, but in this example we also disable the card. Presumably we do not want this person using the card just yet.

+

Deleting a PIV card is no different from + deleting any other kind of credential.

+
+
+
+
+
+ add: + + + PIV card create example + + + +
+
+
+ + + PIV card create example + + + +
+
+
+
+ update: + + + PIV card update example + + + +
+
+
+ + + PIV card update example + + + +
+
+
+
+
+
+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "firstName": "Algernon",
+  "cards": {
+    "add": [
+      {
+        "number": "47000256001337111234567890199991",
+        "status": {
+          "value": "active"
+        },
+        "type": {
+          "href": "https://localhost:8904/api/card_types/244"
+        },
+        "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": "https://localhost:8904/api/cardholders/5398/cards/90e5d0d70",
+        "status": {
+          "value": "disabled (manually)"
+        },
+        "pivData": {
+          "pivStatus": {
+            "type": "notChecked"
+          }
+        }
+      }
+    ]
+  }
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
+
+
+
+

+ Get details of a cardholder +

+
+
+
+ GET + /api/cardholders/{id} +
+
+
+
+
+
+

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.

+

The + PIV card GET schema is below. The + main documentation covers all the other fields Command Centre holds for a cardholder.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the cardholder.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

Success.

+
+
+
+
+
4xx
+
+
+

The operator does not have a privilege that allows reading cardholders, or there is no cardholder at that address.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "many other cardholder fields": "etc",
+  "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": ""
+        },
+        "pivStatus": {
+          "type": "Normal"
+        },
+        "lastCheckTime": "2018-04-26T00:22:05Z",
+        "contentSigningCert": "MIIE[...]Kltk=",
+        "cardAuthenticationCert": "MIIE[...]e5mE=",
+        "pivAuthenticationCert": "MIIE[...]wkrp",
+        "fingerprints": "N7[...]Shpd="
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+

Schema Definitions

+
+

+ PIV card GET: + +

+
+
+
+

In addition to the generic card data the API always returns in the cards section (in the + Cardholders API document) of a cardholder detail page, including href, number, issueLevel, type, and status, PIV cards have a pivData structure.

+

In version 8.00 and later you can check for the value piv on credentialClass.

+
+
+
+
+ href: + string + (url) + +
+
+

This is the same as the href to a card of any other type: you can send an HTTP DELETE to it to delete the card, but that is the only verb that will work. GET will return a 404.

+

Do not specify it when creating a card, since it is the creation process that generates its href.

+
+
+ number: + string + +
+
+

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.

+
+
+ issueLevel: + integer + +
+
+

A PIV card's issue level is derived from data in the CHUID block, presented here for convenience.

+
+
+ status: + object + +
+
+

PIV cards have more inactive card states than other cards. 'Time Invalid (PIV certificate)' or 'Not Trusted', for example. If status.type is 'inactive', status.value will give an indication why.

+

The status of a new card will be 'revoked' if you set the PIV status (which is different from the card status, note) to anything but 'normal' or 'offline'.

+
+
+ pivData: + object + +
+
+

This is much like the + pivData you send when creating a card, described later, except that:

+
    +
  • +

    in certain cases (described below) it does not contain the certificates and biometrics, leaving just the + CHUID block (a subset of the card's CHUID), and the PIV status.

    +
  • +
  • +

    it adds lastCheckTime, which shows when the card was last validated. That is most useful when Command Centre is performing periodic card validation. If you are doing your own validation, this field will hold the moment you last updated the card.

    +
  • +
+

If the site is running 7.90 and the RESTPIVCardExport customisation, or if it is running a later version and you asked for extra data using the fields query parameter, the pivData section will contain three certificates and the fingerprint templates in the correct format for import into another installation of Command Centre.

+

The certificates and biometrics are missing from the default result set in the interest of efficiency: those fields are very large, and most of our integrations do not require them. Some are running over cellular networks, where bandwidth is carries a cost. Omitting the large fields by default but sending them on request is a flexible compromise that allows integrators to build a suitable system without wastage.

+
+
+
+
+
+ chuid: + + + CHUID + + + +
+
+ pivStatus: + object + +
+
+

The type field in this block contains the PIV-specific status of the card, as opposed to the generic credential state which is in a block called status at the same level as pivData.

+

The + schema for PIV card data lists the values it might come back with.

+
+
+ lastCheckTime: + string + (date-time) + +
+
+ contentSigningCert: + string + +
+
+

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.

+
+
+ cardAuthenticationCert: + string + +
+
+

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.

+
+
+ pivAuthenticationCert: + string + +
+
+

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.

+
+
+ fingerprints: + string + +
+
+

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
+ + + +
{
+  "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": ""
+    },
+    "pivStatus": {
+      "type": "Normal"
+    },
+    "lastCheckTime": "2018-04-26T00:22:05Z",
+    "contentSigningCert": "MIIE[...]Kltk=",
+    "cardAuthenticationCert": "MIIE[...]e5mE=",
+    "pivAuthenticationCert": "MIIE[...]wkrp",
+    "fingerprints": "N7[...]Shpd="
+  }
+}
+
+ + +
+
+
+
+
+

+ PIV card type: + +

+
+
+
+

When you + collect card types from Command Centre in preparation for giving a PIV card to someone, the card type you seek will look like this.

+
+
+
+
+ href: + string + (url) + +
+
+

The number in this URL may be different on your system.

+
+
+ name: + string + +
+
+ availableCardStates: + string[] + +
+
+

All credential types have a set of card states. The set assigned to PIV cards contains only two.

+

If you need this, ask for it using the fields parameter.

+
+
+
+ string + + Active, + Disabled (manually) + + +
+
+
+
+ credentialClass: + string + + piv, + card, + mobile + + +
+
+

PIV card types have the credential class 'piv'.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/card_types/243",
+  "name": "PIV Card",
+  "availableCardStates": [
+    "Active",
+    "Disabled (manually)"
+  ],
+  "credentialClass": "piv"
+}
+
+ + +
+
+
+
+
+

+ PIV card create example: + +

+
+
+
+

This example, when placed inside the cards array of a + POST or the cards.add array of a + PATCH, would create a PIV credential and assign it to the cardholder created by the POST or identified by the address of the PATCH. Consult those methods for the JSON you need to wrap around this example.

+

A PIV-I example would have a different style of FASC-N, and a card number to match.

+
+
+
+
+ number: + string + +
+
+

Required.

+

For a PIV card, this is the FASC-N.

+

For a PIV-I card, this is the decimal representation of the GUID in the CHUID, and must not be the same as the FASC-N.

+

The server ignores hyphens here, so you can send back the same number you received from a GET.

+
+
+ status: + object + +
+
+

Optional, but when controlling physical access you should be explicit rather than relying on a default. Set the value field inside it to either 'active' or 'disabled (manually)'. Case insensitive.

+

You will not be able to activate a card if its PIV status (inside the pivdata block) prevents it.

+
+
+ type: + object + +
+
+

Required. This should be the href of the built-in PIV/PIV-I card type, that your application found using the process + in the introduction.

+
+
+ pivData: + + + PIV card data + + + +
+
+

This block contains all the PIV-specific fields. Everything outside this block, including the number, status, and type, is common to all types of Command Centre cards and credentials.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "number": "47000256001337111234567890199991",
+  "status": {
+    "value": "active"
+  },
+  "type": {
+    "href": "https://localhost:8904/api/card_types/244"
+  },
+  "pivData": {
+    "chuid": {
+      "hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
+      "fascn": "47000256001337111234567890199991",
+      "orgIdentifier": "",
+      "duns": ""
+    },
+    "pivStatus": {
+      "type": "Normal"
+    },
+    "contentSigningCert": "MIIE[...]Kltk=",
+    "cardAuthenticationCert": "MIIE[...]e5mE=",
+    "pivAuthenticationCert": "MIIE[...]wkrp",
+    "fingerprints": "N7[...]Shpd="
+  }
+}
+
+ + +
+
+
+
+
+

+ PIV card update example: + +

+
+
+
+

This example, when placed inside cards.update array of a PATCH, would update a card. The main cardholder API documentation shows where to send the PATCH and what else you can change with it. This example only shows how to update the status (common to all credential types) and the PIV data.

+

Specifically, the PIV status, because that 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 for when you should set the PIV status and what you can set it to.

+
+
+
+
+ href: + string + (url) + +
+
+

This is the href of the card you want to update, found in the cards array in the + cardholder detail.

+
+
+ status: + object + +
+
+

Optional. If you omit both this and the PIV status, 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 the card to remain disabled. In that case, set status.value to 'disabled (manually)'.

+
+
+ pivData: + object + +
+
+

This can contain only one field when you are updating a PIV card, pivStatus.type.

+

The + PIV card data schema lists the values you can send.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/cardholders/5398/cards/90e5d0d70",
+  "status": {
+    "value": "disabled (manually)"
+  },
+  "pivData": {
+    "pivStatus": {
+      "type": "notChecked"
+    }
+  }
+}
+
+ + +
+
+
+
+
+

+ PIV card data: + +

+
+
+
+

This example, when placed inside one of the cards in the cards array of a POST or the cards.add array of a PATCH, would create a PIV card. It contains all the PIV-specific fields on a credential.

+

It is the same as the block you + receive from the API for an existing PIV card, minus lastCheckTime.

+
+
+
+
+ chuid: + + + CHUID + + + +
+
+

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

+
+
+ pivStatus: + object + +
+
+

This PIV status is distinct from the 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 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 only if you are doing your own certificate validation. It must contain a field type 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.

+
+
+ contentSigningCert: + string + +
+
+

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.

+
+
+ cardAuthenticationCert: + string + +
+
+

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.

+
+
+ pivAuthenticationCert: + string + +
+
+

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.

+
+
+ fingerprints: + string + +
+
+

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
+ + + +
{
+  "chuid": {
+    "hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
+    "fascn": "47000256001337111234567890199991",
+    "orgIdentifier": "",
+    "duns": ""
+  },
+  "pivStatus": {
+    "type": "Normal"
+  },
+  "contentSigningCert": "MIIE[...]Kltk=",
+  "cardAuthenticationCert": "MIIE[...]e5mE=",
+  "pivAuthenticationCert": "MIIE[...]wkrp",
+  "fingerprints": "N7[...]Shpd="
+}
+
+ + +
+
+
+
+
+

+ CHUID: + +

+
+
+
+

This is an example CHUID block for a PIV card. The only difference between this and a PIV-I card is the FASC-N.

+
+
+
+
+ hash: + string + +
+
+

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 in versions up to 8.60. Optional in 8.70 and later.

+
+
+ fascn: + string + +
+
+

The FASC-N identifier. Required.

+

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

+
+
+ orgIdentifier: + string + +
+
+

Optional.

+
+
+ duns: + string + +
+
+

Optional.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "hash": "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=",
+  "fascn": "47000256001337111234567890199991",
+  "orgIdentifier": "",
+  "duns": ""
+}
+
+ + +
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/ref/rest.html b/ref/rest.html new file mode 100644 index 0000000..2201513 --- /dev/null +++ b/ref/rest.html @@ -0,0 +1,15987 @@ + + + + + + Command Centre REST API: Non-cardholder items | API Reference + + + + + + + +
+ +
+ +
+ +
+
+

Command Centre REST API: Non-cardholder items + API Reference +

+
+
+
+

This document describes how you can use the Command Centre REST API to view and manage access zones, alarm zones, elevator groups, fence zones, doors, macros, inputs, and outputs.

+

It has companion documents describing the + Cardholders and + Alarms and Events APIs, and Command Centre's handling of + PIV cards.

+

It assumes some familiarity with HTTP and REST interfaces. Before you start developing against this interface, you should also read the + Authentication section to learn how to format your queries, and Command Centre's Configuration Client online help to learn how to ready the server to receive them (search for 'REST API').

+

Each section contains a list of use cases intended as quick solutions for simple tasks or a how-to to get you started toward your particular goal. However they also serve as a good introduction to the API if you step through them using a web browser, starting at /api on port 8904 of your Command Centre server. To do that, you will need to set up Command Centre, and install a browser plugin on your web client that lets you set an Authorization HTTP header, and a JSON formatter to prettify the results documents. Search the Configuration client's online help for 'test REST API' for guidance.

+

Licensing

+

In versions up to and including 8.50, all of the GET calls described here require the RESTStatus licence. All the POSTs require RESTOverrides. The two licences do not overlap: to watch an item and override it, you will need both.

+

In 8.60 and later the GETs return a limited field set if you have RESTOverrides but not RESTStatus - enough to let you find the item you want to override.

+

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.

+

API changes on the roadmap

+
    +
  • A future version will sort its search results differently if you do not use a query parameter to override the default.
  • +
+

Changes in 8.80

+ +

Changes in 8.70

+
    +
  • +

    The server returns 1000 items by default, instead of 100.

    +
  • +
  • +

    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.

    +
  • +
+

Changes in 8.60

+
    +
  • The RESTOverrides licence allows you to find items and their override URLs.
  • +
  • You can search for items by description.
  • +
+

Changes in 8.50

+ +

Changes in 8.30

+

All changes to this document contain the text "8.30".

+
    +
  • A 'connectedController' field in the + door detail that returns the ID and name of the door's hardware controller (a C6000 controller, not an API controller).
  • +
+

Changes in 8.20

+

All changes to this document contain the text "8.20".

+
    +
  • An + update_cardholder_location call on the Access Zones API that returns you the list of Access Zones into which your operator is allowed to move cardholders.
  • +
+

Changes in 8.10

+

All changes to this document contain the text "8.10".

+
    +
  • An + inputs API that gives you the state of 'input' hardware items and lets you override them. It is very similar to the outputs API.
  • +
+

Efficiency tips

+
    +
  • +

    When downloading more than one page of items, sort by ID.

    +
  • +
  • +

    Set your page size as large as your client can handle. The default was 100 before 8.70; using the top parameter to take it to 1000 or more gives + much better throughput.

    +
  • +
  • +

    (In v8.00 or later) avoid the details page by adding detail fields to the summary page with the + fields parameter. One summary page of 1,000 items, while huge, is much quicker to retrieve than 1,000 detail pages.

    +

    For example, to get the names, doors, and override commands of all your access zones:

    +

    GET /api/access_zones?fields=name,doors,commands

    +
  • +
  • +

    (In v8.00 or later) save work generating the details page by using the + fields parameter to reduce the amount of data on each page. Be explicit: do not use fields=defaults, unless you need every field that 'defaults' gives you.

    +
  • +
  • +

    When searching for an item by name, if you have its exact name, put it in quotes "...". Otherwise Command Centre will perform a substring search, which is slower and may return more results than you need. Even with quotes, the search is case-insensitive.

    +
  • +
+
+
+
+
Request Content-Types: + application/json +
+
Response Content-Types: + application/json +
+
Schemes: + https +
+
Version: + 1.0.0 +
+
+
+
+
+ +

Authentication

+
+
+
+

+ API key + +

+
+

Clients authenticate by including a pre-shared API key in the Authorization header of each request. Command Centre generates an 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.

+

The API key will be in the format XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX and should be in the header following an authorisation method of GGL-API-KEY and a space. Both should be in upper case. For example:

+

Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB

+

Early versions of Command Centre allowed you to omit the 'GGL-API-KEY'. Later versions of CC and all versions of the API gateway will reject you with a 401 if you do that.

+

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.

+

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.

+

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.

+
+
+
+
+
type
+
+
apiKey
+
+
+
+
name
+
+
Authorization
+
+
+
+
in
+
+
header
+
+
+
+
x-external
+
+
eventsApi.yaml#/securityDefinitions/API key
+
+
+
+
+
+

Documentation suite

+
+
+
+

The API's reference documentation divides into:

+ + + + + + + + + + + + +

+ + + + + +
+ Alarms, events, and non-cardholder items + +

The alarms and events APIs let you download, monitor, and create events, and download, monitor, and manage alarms.

+

The alarms and events documentation also covers API calls that support divisions and items as they relate to events, as well as bulk status monitoring.

+
+ Cardholders and related items + The cardholder parts of the API let you manage your users, their personal data and credentials (cards), and their links to associated items such as access groups, roles, operator groups, competencies, and lockers.
+ Status and overrides + +

These functions let you monitor and override the types of Command Centre items that have their own status, including access zones, alarm zones, doors, fence zones, inputs, outputs, macros, elevator groups, interlock groups, and schedules.

+

Despite its name this section does not cover + mass-monitoring item status.

+ PIV cards + This supplement describes how to work with PIV and PIV-I cards. It is separate from the main cardholder documentation in the interest of brevity.
+
+
+
+

Forward compatibility (HATEOAS)

+
+
+
+

This is a self-referencing REST API that follows the principles of HATEOAS. Other than the initial GET to /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 licensed API calls.

+

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.

+
+
+
+

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.

    +
  • +
+
+
+
+

Field specifiers

+
+
+
+

You can specify the fields you want in the results of a query. This lets you:

+
    +
  • save calls by adding more fields to search results, which are terse by default
  • +
  • save bandwidth by putting fewer fields on detail pages, which are verbose by default, and
  • +
  • retrieve more data by adding fields to detail pages that are not normally there, such as an item's status, short name, and notes.
  • +
+

You do this using the query parameter fields to the search, detail, and updates pages. It takes a comma-separated list of field names. They are case-sensitive: copy them carefully from this document. The special field name defaults adds in the fields the server would have sent had you not set the fields parameter at all.

+

Example GETs:

+
    +
  • +

    /api/cardholders?name=Smith&fields=defaults,cards,competencies will list all cardholders with 'Smith' in the first or last name, showing the fields you normally get on a search page plus their cards and competencies.

    +
  • +
  • +

    /api/access_groups/123?fields=children,cardholders will show just the child groups of access group 123 and a link to retrieve its cardholders - nothing else.

    +
  • +
  • +

    /api/outputs?fields=name,shortname,id will show the name, short name, and ID of your outputs.

    +
  • +
  • +

    /api/access_zones?fields=name,doors will show the name and attached doors of your access zones.

    +
  • +
  • +

    /api/alarms?fields=time,message,details will return the time, message text, and details text of your alarms. Not only does that save a lot of data by removing default fields, but it adds the details field which does not normally come out of an alarm search.

    +
  • +
+

Each controller has different optional fields - see their sections under Operations.

+
+
+
+

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 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 is not fully configured yet. An access zone is not configured until it has at least one door, for example.

    +
  • +
  • +

    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.

+
+
+
+

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. +
  3. An override without an end time lasts until the next mode change or 'cancel untimed overrides' entry in the access zone's schedule.
  4. +
  5. The end time you set for an override cannot be in the past or more than 24 hours into the future.
  6. +
+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Changing an access zone's access mode

+
    +
  1. Find the href for the access zone using the process above.
  2. +
  3. GET it.
  4. +
  5. 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.
  6. +
  7. 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.
  8. +
+

Finding an access zone's status

+
    +
  1. Find the href for the access zone using the process above, and GET it.
  2. +
  3. 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.
  4. +
  5. GET it.
  6. +
  7. Use the flag rules above to interpret the status flags you receive.
  8. +
  9. Follow the next link to stay up to date.
  10. +
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Search access zones +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.accessZones.accessZones section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + doors, + zoneCount, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Search targetable access zones +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.cardholders.updateLocationAccessZones section of /api.

+

Added in 8.20.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + doors, + zoneCount, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Get details of an access zone +

+
+
+
+ GET + /api/access_zones/{id} +
+
+
+
+
+
+

This returns the detail of one access zone.

+

Follow the 'href' field in an + access zone summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

(no description)

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + doors, + zoneCount, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Set an access zone to free +

+
+
+
+ POST + /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.

+

The same URL with _pin on the end 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 it an end time in the body, the override will remain in place until the next scheduled change.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Set an access zone to secure +

+
+
+
+ POST + /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.

+

The same URL with _pin on the end will send an override to the access zone to change its mode to 'secure - PIN', meaning you + will need a PIN on the door and on a terminal.

+

If you do not give it an end time in the body, the override will remain in place until the next scheduled change.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Set an access zone to code or card +

+
+
+
+ POST + /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.

+

The same URL with _pin on the end will override the access zone into 'code or card - PIN', meaning you + will need a PIN when you use a card.

+

If you do not give it an end time in the body, the override will remain in place until the next scheduled change.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Set an access zone to dual auth +

+
+
+
+ POST + /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 cardholders to open its doors, and they will not need a PIN. Terminal functions will require a card but no PIN.

+

The same URL with _pin on the end will override the zone into 'dual auth - PIN' mode, meaning each cardholder will need a PIN for access, and terminal functions will also require a PIN.

+

If you do not give it an end time in the body, the override will remain in place until the next scheduled change.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Forgive antipassback on a zone +

+
+
+
+ POST + /api/access_zones/{id}/forgive_anti_passback +
+
+
+
+
+
+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Lock a zone down +

+
+
+
+ POST + /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.

+

It takes no parameters. The lockdown will remain in place until cancelled, or the access zone receives an override to another mode.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Cancel a zone lockdown +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Set a zone count +

+
+
+
+ POST + /api/access_zones/{id}/set_zone_count +
+
+
+
+
+
+

Sets the count of cardholders inside a zone.

+
+
+
+
+
+
+
+
+ +
+
+ +

The new cardholder count for the zone.

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "zoneCount": 100
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Cancel mode override +

+
+
+
+ POST + /api/access_zones/{id}/cancel +
+
+
+
+
+
+

Cancels an override, returning the access zone to its scheduled state.

+

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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Access Zones + +
+ + +

+ Monitor an access zone +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the access zone.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags, + zoneCount + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

8.50 and earlier: the site does not have the RESTStatus licence.

+

8.60 and later: the site has neither the RESTStatus nor RESTOverrides licence.

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": {
+    "status": "Secure.",
+    "statusText": "Secure.",
+    "statusFlags": [
+      "secure"
+    ],
+    "zoneCount": 100
+  },
+  "next": {
+    "href": "https://localhost:8904/api/access_zones/3280/updates/9_1"
+  }
+}
+
+ + +
+
+
+
+

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. +
  3. 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.
  4. +
  5. 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.
  6. +
  7. The end time you set for an override cannot be in the past or more than 24 hours into the future.
  8. +
+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Changing an alarm zone's state

+
    +
  1. Find the href for the alarm zone using the process above.
  2. +
  3. GET it.
  4. +
  5. 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.
  6. +
  7. 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.
  8. +
+

Finding an alarm zone's status

+
    +
  1. Find the href for the alarm zone using the process above, and GET it.
  2. +
  3. Follow the updates + href from that page.
  4. +
  5. Use the flag rules above to interpret the status flags you receive.
  6. +
  7. Follow the next link to stay up to date.
  8. +
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Search alarm zones +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.alarmZones.alarmZones section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Get details of an alarm zone +

+
+
+
+ GET + /api/alarm_zones/{id} +
+
+
+
+
+
+

This returns the detail of one alarm zone.

+

Follow the 'href' field in an + alarm zone summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Arm an alarm zone +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Disarm an alarm zone +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Change an alarm zone to user1 +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Change an alarm zone to user2 +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Arm an alarm zone (high voltage) +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Arm an alarm zone (low feel) +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Cancel mode override +

+
+
+
+ POST + /api/alarm_zones/{id}/cancel +
+
+
+
+
+
+

Cancels an override, returning the alarm zone to its scheduled state.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Alarm Zones + +
+ + +

+ Monitor an alarm zone +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the alarm zone.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

Success. The + introduction describes the three status fields.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": {
+    "status": "Disarmed.",
+    "statusText": "Disarmed.",
+    "statusFlags": [
+      "disarmed"
+    ]
+  },
+  "next": {
+    "href": "https://localhost:8904/api/alarm_zones/328/updates/9_1"
+  }
+}
+
+ + +
+
+
+
+

Day Categories

+
+
+

A day category links a calendar to 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.

+

Day categories are new to the 8.50 API.

+

Use case: listing day categories

+
    +
  1. GET /api.
  2. +
  3. 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.
  4. +
  5. Search the results for the day category you are after. Normally you would follow the next link until there isn't one, but day categories are not that numerous so they will most likely to fit on one page. Especially if you set top=1000 (which is advised).
  6. +
+
+
+
+ + + + + +

+ Search day categories +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.dayCategories.dayCategories section of /api.

+

Added in 8.50.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + name, + description, + notes + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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."
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/day_categories?skip=1000"
+  }
+}
+
+ + +
+
+
+
+

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

+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Opening a Door

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

Finding a door's status

+
    +
  1. Find the href for the door using the process above, and GET it.
  2. +
  3. Follow the updates href from that page.
  4. +
  5. Use the flag rules above to interpret the status flags you receive.
  6. +
  7. Follow the next link to stay up to date.
  8. +
+
+
+
+ + +
+ Doors + +
+ + +

+ Search doors +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.doors.doors section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + entryAccessZone, + exitAccessZone, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Door search + +
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/doors/332",
+      "id": "332",
+      "name": "Front door"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/doors?skip=1000"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Doors + +
+ + +

+ Get details of a door +

+
+
+
+ GET + /api/doors/{id} +
+
+
+
+
+
+

This returns the detail of one door.

+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the door.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + entryAccessZone, + exitAccessZone, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Door detail + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+  },
+  "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"
+    }
+  },
+  "connectedController": {
+    "name": "Third floor C6000",
+    "href": "https://localhost:8904/api/items/508",
+    "id": "634"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Doors + +
+ + +

+ Open a door +

+
+
+
+ POST + /api/doors/{id}/open +
+
+
+
+
+
+

Sends an override to unlock a door.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the door.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Doors + +
+ + +

+ Monitor a door +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the door.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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. +
  3. 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.
  4. +
  5. Find the elevator group you are after, following the next link if you have lots of elevator groups.
  6. +
  7. 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.
  8. +
+

Setting a cardholder's default floor

+
    +
  1. + Find the href for the cardholder.
  2. +
  3. + 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.
  4. +
+
+
+
+ + + + + +

+ Search assignable elevator groups +

+
+
+
+ GET + /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.

+

Unlike most of the other calls in this document, it requires the RESTCardholders licence.

+

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.

+

Take this URL from the href field in the features.cardholders.modifyDefaultFloors section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + name, + description, + division, + floorAccess + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

Success. An array of [elevator group(#definition-Elevator-Group-Floor-Access-detail) objects and a next link for more.

+

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

+
+
+
+
+
403 Forbidden
+
+
+

The site does not have the RESTCardholders licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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 + /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.

+

Take this URL from the href field in the features.elevators.elevatorGroups section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + name, + shortName, + description, + division, + notes, + elevatorSystem, + elevatorGroupNumber, + floorAccess, + rearAccessEnabled, + groundFloorNumber + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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 a elevator group +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the elevator group.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + name, + shortName, + description, + division, + notes, + elevatorSystem, + elevatorGroupNumber, + floorAccess, + rearAccessEnabled, + groundFloorNumber + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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
+}
+
+ + +
+
+
+
+

Fence Zones

+
+
+

These methods give you read access to Fence Zones in the Command Centre database, and let you 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.

+

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 energising a hardware technician.
  • +
  • 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.
  • +
+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Overriding a fence zone

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

Finding a fence zone's status

+
    +
  1. Find the href for the fence zone using the process above, and GET it.
  2. +
  3. 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.
  4. +
  5. GET it.
  6. +
  7. Use the flag rules above to interpret the status flags you receive.
  8. +
  9. Follow the next link to stay up to date.
  10. +
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Search fence zones +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.fenceZones.fenceZones section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + voltage, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/fence_zones/8487",
+      "id": "8487",
+      "name": "Storage yard"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/fence_zones?skip=1000"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Get details of a fence zone +

+
+
+
+ GET + /api/fence_zones/{id} +
+
+
+
+
+
+

This returns the detail of one fence zone.

+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + description, + division, + commands, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Turn on a fence zone +

+
+
+
+ POST + /api/fence_zones/{id}/on +
+
+
+
+
+
+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Turn off a fence zone +

+
+
+
+ POST + /api/fence_zones/{id}/off +
+
+
+
+
+
+

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

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Shunt a fence zone +

+
+
+
+ POST + /api/fence_zones/{id}/shunt +
+
+
+
+
+
+

Sends an override to an alarm zone to shunt it, effectively preventing all communication with it.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Unshunt a fence zone +

+
+
+
+ POST + /api/fence_zones/{id}/unshunt +
+
+
+
+
+
+

Sends an override to an alarm zone to unshunt it, re-enabling its communication.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Change to high voltage +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Change to low feel +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Cancel an override +

+
+
+
+ POST + /api/fence_zones/{id}/cancel +
+
+
+
+
+
+

Cancels an active override.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Fence Zones + +
+ + +

+ Monitor a fence zone +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the fence zone.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags, + voltage + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

Success. See the + introduction for a description of the three status fields and the + detail page for voltage.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": {
+    "status": "On - HV.",
+    "statusText": "On - HV.",
+    "statusFlags": [
+      "on",
+      "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.

+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link if there is one.
  6. +
+

Overriding an Input

+
    +
  1. Find the href for the input using the process above.
  2. +
  3. GET it.
  4. +
  5. Find the API URL for the override you need in the commands structure of the + results.
  6. +
  7. POST to that URL with an empty body.
  8. +
+

Finding an input's status

+
    +
  1. Find the href for the input using the process above, and GET it.
  2. +
  3. Take the updates + href from that page.
  4. +
  5. GET it.
  6. +
  7. Use the flag rules above to interpret the status flags you receive.
  8. +
  9. Follow the next link to stay up to date.
  10. +
+
+
+
+ + +
+ Inputs + +
+ + +

+ Search inputs +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.inputs.inputs section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Input search + +
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/inputs/9701",
+      "id": "9701",
+      "name": "Studio door open sensor"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/inputs?skip=1000"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Inputs + +
+ + +

+ Get details of an input +

+
+
+
+ GET + /api/inputs/{id} +
+
+
+
+
+
+

This returns the detail of one input.

+

Follow the 'href' field in an + input summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the input.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Input detail + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Inputs + +
+ + +

+ Shunt an input +

+
+
+
+ POST + /api/inputs/{id}/shunt +
+
+
+
+
+
+

Sends an override to shunt an input, preventing all communication.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the input.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + +
+ Inputs + +
+ + +

+ Unshunt an input +

+
+
+
+ POST + /api/inputs/{id}/unshunt +
+
+
+
+
+
+

Sends an override to unshunt an input, re-enabling communication.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the input.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + +
+ Inputs + +
+ + +

+ Isolate an input +

+
+
+
+ POST + /api/inputs/{id}/isolate +
+
+
+
+
+
+

Sends an override to isolate an input. An isolated input will not prevent an alarm zone from arming.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the input.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + +
+ Inputs + +
+ + +

+ De-isolate an input +

+
+
+
+ POST + /api/inputs/{id}/deisolate +
+
+
+
+
+
+

Sends an override to end the isolation of an input.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the input.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + +
+ Inputs + +
+ + +

+ Monitor an input +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the input.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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.

+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link if there is one.
  6. +
+

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. +
  3. Create a + status subscription for those items.
  4. +
+

Finding the status of an interlock group

+
    +
  1. Find the href for the interlock using the process above, and GET it.
  2. +
  3. Take the updates href from that page.
  4. +
  5. GET it.
  6. +
  7. Use the flag rules above to interpret the status flags you receive.
  8. +
  9. Follow the next link to stay up to date.
  10. +
+

Overriding an Interlock Group

+
    +
  1. Find the href for the interlock using the process above.
  2. +
  3. GET it.
  4. +
  5. Find the API URL for the override you need (disable or re-enable) in the commands structure of the + results.
  6. +
  7. POST to that URL with an empty body.
  8. +
+
+
+
+ + + + + +

+ Search interlock groups +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.interlockGroups.interlockGroups section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

The site has neither the RESTStatus nor the RESTOverrides licence.

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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 + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the interlock group.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

The site has neither the RESTStatus nor the RESTOverrides licence.

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": "Third floor C6000",
+    "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 + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the interlock group.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + + + + +

+ Re-enable an interlock group. +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the interlock group.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
+
+ + + + + +

+ Monitor an interlock group. +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the interlock group.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": {
+    "statusFlags": [
+      "open"
+    ]
+  },
+  "next": {
+    "href": "https://localhost:8904/api/interlock_groups/122322/updates/9_1"
+  }
+}
+
+ + +
+
+
+
+

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.

+

Overrides

+

You cannot use the REST API to change a macro's schedule. You must use the Command Centre or Configuration client for that.

+

Use cases

+

Listing Macros

+
    +
  1. GET /api.
  2. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Running a Macro

+
    +
  1. Find the href for the macro using the process above.
  2. +
  3. GET it.
  4. +
  5. 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.
  6. +
+
+
+
+ + +
+ Macros + +
+ + +

+ Search macros +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.macros.macros section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + description, + division, + commands, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Macro search + +
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/macros/8492",
+      "id": "8492",
+      "name": "Arm lobby"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/macros?skip=1000"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Macros + +
+ + +

+ Get details of a macro +

+
+
+
+ GET + /api/macros/{id} +
+
+
+
+
+
+

This returns the detail of one macro.

+

Follow the 'href' field in an + macro summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the macro.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + description, + division, + commands, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+ + Macro detail + +
+ +
+
+

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

+
+
+
+
+
404 Not 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'.

+
+
+
+
+
+
+
Response Example + (403 Forbidden) +
+ + + +
{
+  "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Macros + +
+ + +

+ Run a macro +

+
+
+
+ POST + /api/macros/{id}/run +
+
+
+
+
+
+

Sends a run request to a macro.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the macro.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+

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. +
  3. The end time you set for an override cannot be in the past or more than 24 hours into the future.
  4. +
+

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

+

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. +
  3. 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.
  4. +
  5. Process the results, following the next link until there isn't one.
  6. +
+

Switching an Output

+
    +
  1. Find the href for the output using the process above.
  2. +
  3. GET it.
  4. +
  5. 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.
  6. +
  7. POST to that URL. Those with until in their command block keys require a JSON object in the body; the others expect it empty.
  8. +
+

Finding an output's status

+
    +
  1. Find the href for the output using the process above, and GET it.
  2. +
  3. Take the updates + href from that page.
  4. +
  5. GET it.
  6. +
  7. Use the flag rules above to interpret the status flags you receive.
  8. +
  9. Follow the next link to stay up to date.
  10. +
+
+
+
+ + +
+ Outputs + +
+ + +

+ Search outputs +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.outputs.outputs section of /api.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Output search + +
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "results": [
+    {
+      "href": "https://localhost:8904/api/outputs/2365",
+      "id": "2365",
+      "name": "Studio door red/green"
+    }
+  ],
+  "next": {
+    "href": "https://localhost:8904/api/outputs?skip=1000"
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Outputs + +
+ + +

+ Get details of an output +

+
+
+
+ GET + /api/outputs/{id} +
+
+
+
+
+
+

This returns the detail of one output.

+

Follow the 'href' field in an + output summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the output.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + id, + name, + shortName, + description, + division, + commands, + connectedController, + statusFlags, + statusText, + status, + notes, + updates + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Output detail + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+ + +
+ Outputs + +
+ + +

+ Turn on an output +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the output.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Outputs + +
+ + +

+ Pulse an output +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the output.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

The output is not configured for pulsing (8.70 and later only).

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Outputs + +
+ + +

+ Turn off an output +

+
+
+
+ POST + /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.

+
+
+
+
+
+
+
+
+ +
+
+ +

Optional

+ +
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the output.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
Request Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
400 Bad Request
+
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Outputs + +
+ + +

+ Cancel an override +

+
+
+
+ POST + /api/outputs/{id}/cancel +
+
+
+
+
+
+

Cancels an override, returning the output to its previous state.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the output.

+
+
+
+
+
requested_by
+
in query
+
+ string + + none +
+
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
+
+ + +
+ Outputs + +
+ + +

+ Monitor an output +

+
+
+
+ GET + /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.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the output.

+
+
+
+
+
fields
+
in query
+
+ string + + status, + statusText, + statusFlags + + +
+
+
+

Returns these fields in the update, instead of the default set. Note that removing fields also saves you from updates to those fields.

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "updates": {
+    "status": "This Output is Off.",
+    "statusText": "This Output is Off.",
+    "statusFlags": [
+      "open"
+    ]
+  },
+  "next": {
+    "href": "https://localhost:8904/api/outputs/2365/updates/9_1"
+  }
+}
+
+ + +
+
+
+
+

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 and RESTOverrides give you all schedule types.

+
+
+
+ + +
+ Schedules + +
+ + +

+ Search schedules +

+
+
+
+ GET + /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.

+

Take this URL from the 'href' field in the features.schedules.schedules section of /api.

+

Added in 8.50.

+
+
+
+
+
+
+
+
+
sort
+
in query
+
+ string + + id, + name, + -id, + -name + + +
+
+
+

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. Sorting by ID does not carry that risk.
  2. +
  3. Following a next link is + dramatically quicker when sorting by ID.
  4. +
+

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.

+
+
+
+
+
top
+
in query
+
+ integer + x ≥ 1 +
+
+
+

Limits the results to no more than this many items per page.

+

Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of performance versus page size begins to level out.

+
+
+
+
+
name
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
division
+
in query
+
+ string[] + +
+
+
+

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.

+

List the IDs of the divisions you are interested in separated by 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.

+
+
+
+
+
description
+
in query
+
+ string + +
+
+
+

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.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + name, + description, + division, + notes, + type, + dayCategories + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+ + +
+
+

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

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "href": "https://localhost:8904/api/schedules/6",
+  "name": "Default Access Zone Secure"
+}
+
+ + +
+
+
+
+
+ + +
+ Schedules + +
+ + +

+ Create a schedule +

+
+
+
+ POST + /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.

+

Take this URL from the 'href' field in the features.schedules.schedules section of /api.

+

Added in 8.50.

+
+
+
+
+
+
+
+ +
+ +

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.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "Default Access Zone Secure",
+  "description": "Secure 24/7",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "A default schedule",
+  "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"
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+
+
+
+
+
201 Created
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
+
+
Response Headers + (201 Created) +
+
+ + + + + + + + + + + + + + +
location + +

The href of the new schedule.

+
+ string + (url) + +
+
+
+
+
+
+
+ + +
+ Schedules + +
+ + +

+ Get details of a schedule +

+
+
+
+ GET + /api/schedules/{id} +
+
+
+
+
+
+

This returns the detail of one schedule.

+

Follow the 'href' field in an + schedule summary to get here.

+
+
+
+
+
+
+
+
+
id
+ +
in path
+
+ string + +
+
+
+

The ID of the schedule.

+
+
+
+
+
fields
+
in query
+
+ string + + href, + name, + description, + division, + notes, + type, + dayCategories + + +
+
+
+

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

+
+
+
+
+
+
+
+
+
+
+
+
200 OK
+
+ + Schedule detail + +
+ +
+
+

Success.

+
+
+
+
+
403 Forbidden
+
+
+

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

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
Response Example + (200 OK) +
+ + + +
{
+  "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"
+    }
+  ]
+}
+
+ + +
+
+
+
+
+ + +
+ Schedules + +
+ + +

+ Modify a schedule +

+
+
+
+ PATCH + /api/schedules/{id} +
+
+
+
+
+
+

Modifies a schedule according to the body of the PATCH.

+

Added in 8.50.

+
+
+
+
+
+
+
+ +
+ +

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.

+ +
+
+
+
+
+
+
Request Example
+ + + +
{
+  "name": "Default Access Zone Secure",
+  "description": "Secure 24/7",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "A default schedule",
+  "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"
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+
+
+
+
+
200 OK
+
+
+

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

+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
+
+ + +
+ Schedules + +
+ + +

+ Delete a schedule +

+
+
+
+ DELETE + /api/schedules/{id} +
+
+
+
+
+
+

Deletes the schedule identified by the request's URL.

+

Added in 8.50.

+
+
+
+
+
+
+
+
+
+
+
+
+
204 No Content
+
+
+

Success.

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

+
+
+
+
+
403 Forbidden
+
+
+

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.

+
+
+
+
+
404 Not 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'."

+
+
+
+
+
+
+
+

Schema Definitions

+ +
+

+ Access Zones as move targets: + +

+
+
+
+

An array of access zones, and a link to a special 'outside the system' access zone. This is the list of access zones into which your operator has the privilege to move cardholders.

+
+
+
+
+ results: + + + Access Zone summary + + + +
+
+

An array of Access Zone summaries, just as you would receive from the Access Zone search GET.

+
+
+
+ + + Access Zone summary + + + +
+
+
+
+ outsideOfSystem: + object + +
+
+

This contains the href you should use to move a cardholder out of all Access Zones.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ next: + object + +
+
+

The link to the next page. Absent if you have retrieved them all.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
Example
+ + + +
{
+  "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"
+  }
+}
+
+ + +
+
+
+
+
+

+ Access Zone summary: + +

+
+
+
+

/api/access_zones returns an array of these. It is a subset of what you get from a access zone's detail page at /api/access_zones/{id} (linked as the href in this object).

+
+
+
+
+ href: + string + (url) + +
+
+

A link to an + access zone detail object for this access zone. This is Command Centre's identifier for this access zone: use it whenever you need to specify an access zone in REST operations.

+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/access_zones/3280",
+  "id": "3280",
+  "name": "Roswell building 2 lobby"
+}
+
+ + +
+
+
+
+
+

+ Access Zone detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this Access Zone.

+
+
+ doors: + object[] + +
+
+

A list containing names of and links to the doors that control entry to this access zone.

+
+
+
+ object + +
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

This is the link to the + door's detail (which contains a return link back to this page).

+
+
+
+
+
+
+
+ zoneCount: + integer + +
+
+

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

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ commands: + object + +
+
+

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

+
+
+
+
+
+ free: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'free - no PIN' access mode until the next scheduled change.

+
+
+
+
+
+ freeUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'free - no PIN' access mode for a fixed time.

+
+
+
+
+
+ freePin: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'free - PIN' access mode until the next scheduled change.

+
+
+
+
+
+ freePinUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'free - PIN' access mode for a fixed time.

+
+
+
+
+
+ secure: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'secure - no PIN' access mode until the next scheduled change.

+
+
+
+
+
+ secureUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'secure - no PIN' access mode for a fixed time.

+
+
+
+
+
+ securePin: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'secure - PIN' access mode until the next scheduled change.

+
+
+
+
+
+ securePinUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'secure - PIN' access mode for a fixed time.

+
+
+
+
+
+ codeOnly: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'code or card - no PIN' access mode until the next scheduled change.

+
+
+
+
+
+ codeOnlyUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'code or card - no PIN' access mode for a fixed time.

+
+
+
+
+
+ codeOnlyPin: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'code or card - PIN' access mode until the next scheduled change.

+
+
+
+
+
+ codeOnlyPinUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'code or card - PIN' access mode for a fixed time.

+
+
+
+
+
+ dualAuth: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'dual auth - no PIN' access mode until the next scheduled change.

+
+
+
+
+
+ dualAuthUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'dual auth - no PIN' access mode for a fixed time.

+
+
+
+
+
+ dualAuthPin: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'dual auth - PIN' access mode until the next scheduled change.

+
+
+
+
+
+ dualAuthPinUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to override the zone into 'dual auth - PIN' access mode for a fixed time.

+
+
+
+
+
+ forgiveAntiPassback: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to forgive anti-passback for all cardholders in the zone.

+
+
+
+
+
+ setZoneCount: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this set the zone's cardholder count.

+
+
+
+
+
+ lockDown: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this set the zone into lockdown mode.

+
+
+
+
+
+ cancelLockDown: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this cancel lockdown on the zone, returning it to its scheduled mode.

+
+
+
+
+
+ cancel: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+
+

+ Access Zone count: object + +

+
+
+
+
+
+ zoneCount: + integer + + +
+
+

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

+
+
+
+
+
+
+
Example
+ + + +
{
+  "zoneCount": 100
+}
+
+ + +
+
+
+
+ +
+

+ Alarm Zone summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/alarm_zones/328",
+  "id": "328",
+  "name": "Roswell building 2 lobby alarms"
+}
+
+ + +
+
+
+
+
+

+ Alarm Zone detail: + +

+
+
+
+

+ /api/alarm_zones/{id} returns one of these.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this Alarm Zone.

+
+
+ shortName: + string + + (up to 16 chars) +
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ commands: + object + +
+
+

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

+
+
+
+
+
+ arm: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to arm this alarm zone until the next scheduled change.

+
+
+ name: + string + + Armed, + Set + + + Armed +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ armUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to arm this alarm zone for a fixed time.

+
+
+ name: + string + + Armed, + Set + + + Armed +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ disarm: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to disarm this alarm zone until the next scheduled change.

+
+
+ name: + string + + Disarmed, + Unset + + + Disarmed +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ disarmUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to disarm this alarm zone for a fixed time.

+
+
+ name: + string + + Disarmed, + Unset + + + Disarmed +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ user1: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to set the alarm zone's state to user1 until the next scheduled change.

+
+
+ name: + string + + User1 +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ user1Until: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to set the alarm zone's state to user1 for a fixed time.

+
+
+ name: + string + + User1 +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ user2: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to set the alarm zone's state to user2 until the next scheduled change.

+
+
+ name: + string + + User2 +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ user2Until: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to set the alarm zone's state to user2 for a fixed time.

+
+
+ name: + string + + User2 +
+
+

Because a site can configure different names for different alarm zone states, they appear here.

+
+
+
+
+
+ cancel: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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"
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Day category: + +

+
+
+
+

/api/day_categories returns an array of these. Each gives you enough about a day category to identify it and use it in a schedule: its href, name, and (if you ask for them using the fields parameter) notes and description.

+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

This is the string to use when placing a day category on a schedule.

+
+
+ description: + string + +
+
+

Not in the default field set. If you want it, you need to ask for it using fields.

+
+
+ notes: + string + +
+
+

Also not in the default field set. If you want it, you need to ask for it.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Default Day Category",
+  "href": "https://localhost:8904/api/day_categories/3",
+  "description": "Factory default",
+  "notes": "The default calendar puts every day in this."
+}
+
+ + +
+
+
+
+ +
+

+ Door summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A link to a + door detail object for this door.

+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/doors/332",
+  "id": "332",
+  "name": "Front door"
+}
+
+ + +
+
+
+
+
+

+ Door detail: + +

+
+
+
+

+ /api/doors/{id} returns one of these.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this door.

+
+
+ entryAccessZone: + object + +
+
+

The name and href of the access zone to which this door allows entry.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

This is the address of the + access zone's detail page (which includes a link back here).

+
+
+
+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ commands: + object + +
+
+

An array of commands, each represented by a block containing an href that accepts a POST to send an override. The only override you can send to a door is 'open'.

+

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.

+
+
+
+
+
+ open: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to open this door.

+
+
+
+
+
+
+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

This is the REST API's identifier for the hardware controller. It is only an identifier, not a usable URL, because in 8.30 there is no interface for hardware controllers. GETting the URL will return a 404.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+
+
+
+
+
+
+
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"
+  },
+  "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"
+    }
+  },
+  "connectedController": {
+    "name": "Third floor C6000",
+    "href": "https://localhost:8904/api/items/508",
+    "id": "634"
+  }
+}
+
+ + +
+
+
+
+
+

+ Elevator Group summary: + +

+
+
+
+

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 'modify passenger details' search or the elevator group's detail page (which is linked as the href in this object).

+
+
+
+
+ href: + string + (url) + +
+
+

A link to a + elevator group object for this elevator group.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/elevator_groups/635",
+  "name": "Main building lower floors"
+}
+
+ + +
+
+
+
+
+

+ Elevator Group Floor Access detail: + +

+
+
+
+

The + modify passenger details search returns an array of these. It is everything you need to pick a default floor for a cardholder, but a subset of what you get from a elevator group's detail page (which is linked as the href in this object).

+
+
+
+ +
+
+
+
+
+ division: + object + +
+
+

The division containing this elevator group.

+
+
+ floorAccess: + object[] + +
+
+

An array of objects describing the floors in this elevator group.

+

This example only has one floor. Expect more from production systems.

+
+
+
+ object + +
+
+
+
+ floorNumber: + integer + x ≥ 1 +
+
+

This is Command Centre's internal identifier for the floor. Passengers do not see it.

+
+
+ frontService: + boolean + +
+
+

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.

+
+
+ rearService: + boolean + +
+
+

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.

+
+
+ floorName: + string + +
+
+

This is a friendly name that Command Centre presents to operators as the name of the floor.

+
+
+ frontAccessZone: + object + +
+
+

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

+
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+
+
+
+ rearAccessZone: + object + +
+
+

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.

+
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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"
+      }
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Elevator Group detail: + +

+
+
+
+

+ /api/elevator_groups/{id} returns one of these.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ elevatorGroupNumber: + integer + +
+
+

The elevator system's internal identifier for this elevator group.

+
+
+ elevatorSystem: + object + +
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ rearAccessEnabled: + boolean + +
+
+

True only if the elevator group uses rear doors on its cars to service the other side of the shaft.

+
+
+ groundFloorNumber: + integer + x ≥ 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
+}
+
+ + +
+
+
+
+ +
+

+ Fence Zone summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/fence_zones/8487",
+  "id": "8487",
+  "name": "Storage yard"
+}
+
+ + +
+
+
+
+
+

+ Fence Zone detail: + +

+
+
+
+

+ /api/fence_zones/{id} returns one of these.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this Fence Zone.

+
+
+ voltage: + integer + x ≥ 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,....

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ commands: + object + +
+
+

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.

+
+
+
+
+
+ on: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to energise this fence zone.

+
+
+
+
+
+ off: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to deactivate this fence zone.

+
+
+
+
+
+ shunt: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to shunt this fence zone.

+
+
+
+
+
+ unshunt: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to restore communication with fence zone.

+
+
+
+
+
+ highVoltage: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to set this fence zone to 'high voltage' mode.

+
+
+
+
+
+ lowFeel: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to set this fence zone to 'low feel' mode.

+
+
+
+
+
+ cancel: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Input summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A link to an + input detail object for this input.

+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/inputs/9701",
+  "id": "9701",
+  "name": "Studio door open sensor"
+}
+
+ + +
+
+
+
+
+

+ Input detail: + +

+
+
+
+

+ /api/inputs/{id} returns one of these.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this input.

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ commands: + object + +
+
+

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

+
+
+
+
+
+ shunt: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to shunt the input.

+
+
+
+
+
+ unshunt: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to stop shunting the input.

+
+
+
+
+
+ isolate: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

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

+
+
+
+
+
+ deisolate: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Interlock Group summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A link to an + interlock group detail object for this item.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/interlock_group/122322",
+  "name": "Excercise yard egress"
+}
+
+ + +
+
+
+
+
+

+ Interlock Group detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ id: + string + +
+
+

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.

+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this interlock group.

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ statusText: + string + +
+
+

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.

+
+
+ status: + string + +
+
+

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.

+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ commands: + object + +
+
+

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

+
+
+
+
+
+ disable: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to disable the interlock group, allowing the doors to operate independently.

+
+
+
+
+
+ enable: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Macro summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A link to a + macro detail object for this macro.

+
+
+ id: + string + +
+
+

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

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/macros/8492",
+  "id": "8492",
+  "name": "Arm lobby"
+}
+
+ + +
+
+
+
+
+

+ Macro detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this Macro.

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ commands: + object + +
+
+

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.

+
+
+
+
+
+ run: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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"
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Output summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A link to an + output detail object for this output.

+
+
+ id: + string + +
+
+

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.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/outputs/2365",
+  "id": "2365",
+  "name": "Studio door red/green"
+}
+
+ + +
+
+
+
+
+

+ Output detail: + +

+
+
+
+

+ /api/outputs/{id} returns one of these.

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this output.

+
+
+ shortName: + string + + (up to 16 chars) +
+
+

Short names are not displayed by default. You must ask for them using the 'fields' parameter:

+

?fields=shortname,....

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ updates: + object + +
+
+

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.

+
+
+
+
+
+ href: + string + (url) + +
+
+
+
+
+ statusFlags: + 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.

+
+
+
+ string + +
+
+
+
+ connectedController: + object + +
+
+

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.

+
+
+
+
+
+ name: + string + +
+
+ href: + string + (url) + +
+
+

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.

+
+
+ id: + string + +
+
+

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.

+
+
+
+
+
+ commands: + object + +
+
+

A block of commands, each represented by a block 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).

+
+
+
+
+
+ on: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to close this output.

+
+
+
+
+
+ onUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to close this output for a fixed time.

+
+
+
+
+
+ off: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to open this output.

+
+
+
+
+
+ offUntil: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ POST to this to open this output for a fixed time.

+
+
+
+
+
+ pulse: + object + +
+
+
+
+
+ href: + string + (url) + +
+
+

+ 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": "Third floor C6000",
+    "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"
+    }
+  }
+}
+
+ + +
+
+
+
+ +
+

+ Schedule summary: + +

+
+
+
+

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

+
+
+
+
+ href: + string + (url) + +
+
+

A link to a + schedule detail.

+
+
+ name: + string + +
+
+
+
+
+
+
Example
+ + + +
{
+  "href": "https://localhost:8904/api/schedules/6",
+  "name": "Default Access Zone Secure"
+}
+
+ + +
+
+
+
+
+

+ Schedule detail: + +

+
+
+
+

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

+
+
+
+ +
+
+
+
+
+ description: + string + +
+
+ division: + object + +
+
+

The division containing this schedule. It is possible to change a schedule's division after creating it.

+
+
+ notes: + string + +
+
+

Because of their potential size, notes are only available by request. Use the 'fields' parameter:

+

?fields=defaults,notes,...

+
+
+ type: + object + +
+
+

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.

+
+
+
+
+
+ type: + string + + accessZoneSchedule, + accessSchedule, + alarmZoneSchedule, + outputSchedule, + notificationSchedule, + hVLFSchedule, + elevatorKioskControlSchedule + + +
+
+
+
+
+ dayCategories: + array + +
+
+

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.

+
+
+ scheduledItems: + array + +
+
+

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.

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

+ Schedule POST and PATCH: + +

+
+
+
+

+ /api/schedules expects one of these in the body of a POST or a PATCH.

+

The PATCH will ignore the type field, because a schedule cannot change its type.

+
+
+
+
+ name: + string + +
+
+ description: + string + +
+
+ division: + object + +
+
+

The division into which you want to place this schedule.

+
+
+ notes: + string + +
+
+ type: + object + +
+
+

This block contains a string field type which must 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 version 8.60.

+
+
+
+
+
+ type: + string + + accessZoneSchedule, + accessSchedule, + alarmZoneSchedule, + outputSchedule, + notificationSchedule, + hVLFSchedule, + elevatorKioskControlSchedule + + +
+
+
+
+
+ dayCategories: + array + +
+
+

See the description in the + details page.

+

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.

+
+
+
+
+
+
+
Example
+ + + +
{
+  "name": "Default Access Zone Secure",
+  "description": "Secure 24/7",
+  "division": {
+    "href": "https://localhost:8904/api/divisions/2"
+  },
+  "notes": "A default schedule",
+  "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"
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+ + +
+
+
+
+
+

+ Override end time: object + +

+
+
+
+
+
+ endTime: + string + (date-time) + +
+
+

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

+
+
+
+
+
+
+
Example
+ + + +
{
+  "endTime": "2018-07-31T00:00:00Z"
+}
+
+ + +
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/ref/stylesheets/foundation.min.css b/ref/stylesheets/foundation.min.css new file mode 100644 index 0000000..9cc44fa --- /dev/null +++ b/ref/stylesheets/foundation.min.css @@ -0,0 +1 @@ +@charset "UTF-8";/*! normalize-scss | MIT/GPLv2 License | bit.ly/normalize-scss */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}main{display:block}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}input{overflow:visible}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;display:table;max-width:100%;padding:0;color:inherit;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}details{display:block}summary{display:list-item}menu{display:block}canvas{display:inline-block}template{display:none}[hidden]{display:none}.foundation-mq{font-family:"small=0em&medium=40em&large=64em&xlarge=75em&xxlarge=90em"}html{box-sizing:border-box;font-size:95%}*,::after,::before{box-sizing:inherit}body{margin:0;padding:0;background:#fefefe;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:400;line-height:1.5;color:#181818;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}img{display:inline-block;vertical-align:middle;max-width:100%;height:auto;-ms-interpolation-mode:bicubic}textarea{height:auto;min-height:50px;border-radius:0}select{box-sizing:border-box;width:100%;border-radius:0}.map_canvas embed,.map_canvas img,.map_canvas object,.mqa-display embed,.mqa-display img,.mqa-display object{max-width:none!important}button{padding:0;appearance:none;border:0;border-radius:0;background:0 0;line-height:1}[data-whatinput=mouse] button{outline:0}pre{overflow:auto}button,input,optgroup,select,textarea{font-family:inherit}.is-visible{display:block!important}.is-hidden{display:none!important}.row{max-width:auto;margin-right:auto;margin-left:auto}.row::after,.row::before{display:table;content:' ';flex-basis:0;order:1}.row::after{clear:both}.row.collapse>.column,.row.collapse>.columns{padding-right:0;padding-left:0}.row .row{margin-right:-.6578947368rem;margin-left:-.6578947368rem}@media print,screen and (min-width:40em){.row .row{margin-right:-.9868421053rem;margin-left:-.9868421053rem}}@media print,screen and (min-width:64em){.row .row{margin-right:-.9868421053rem;margin-left:-.9868421053rem}}.row .row.collapse{margin-right:0;margin-left:0}.row.expanded{max-width:none}.row.expanded .row{margin-right:auto;margin-left:auto}.row:not(.expanded) .row{max-width:none}.row.gutter-small>.column,.row.gutter-small>.columns{padding-right:.6578947368rem;padding-left:.6578947368rem}.row.gutter-medium>.column,.row.gutter-medium>.columns{padding-right:.9868421053rem;padding-left:.9868421053rem}.column,.columns{width:100%;float:left;padding-right:.6578947368rem;padding-left:.6578947368rem}@media print,screen and (min-width:40em){.column,.columns{padding-right:.9868421053rem;padding-left:.9868421053rem}}.column:last-child:not(:first-child),.columns:last-child:not(:first-child){float:right}.column.end:last-child:last-child,.end.columns:last-child:last-child{float:left}.column.row.row,.row.row.columns{float:none}.row .column.row.row,.row .row.row.columns{margin-right:0;margin-left:0;padding-right:0;padding-left:0}.small-1{width:8.3333333333%}.small-push-1{position:relative;left:8.3333333333%}.small-pull-1{position:relative;left:-8.3333333333%}.small-offset-0{margin-left:0}.small-2{width:16.6666666667%}.small-push-2{position:relative;left:16.6666666667%}.small-pull-2{position:relative;left:-16.6666666667%}.small-offset-1{margin-left:8.3333333333%}.small-3{width:25%}.small-push-3{position:relative;left:25%}.small-pull-3{position:relative;left:-25%}.small-offset-2{margin-left:16.6666666667%}.small-4{width:33.3333333333%}.small-push-4{position:relative;left:33.3333333333%}.small-pull-4{position:relative;left:-33.3333333333%}.small-offset-3{margin-left:25%}.small-5{width:41.6666666667%}.small-push-5{position:relative;left:41.6666666667%}.small-pull-5{position:relative;left:-41.6666666667%}.small-offset-4{margin-left:33.3333333333%}.small-6{width:50%}.small-push-6{position:relative;left:50%}.small-pull-6{position:relative;left:-50%}.small-offset-5{margin-left:41.6666666667%}.small-7{width:58.3333333333%}.small-push-7{position:relative;left:58.3333333333%}.small-pull-7{position:relative;left:-58.3333333333%}.small-offset-6{margin-left:50%}.small-8{width:66.6666666667%}.small-push-8{position:relative;left:66.6666666667%}.small-pull-8{position:relative;left:-66.6666666667%}.small-offset-7{margin-left:58.3333333333%}.small-9{width:75%}.small-push-9{position:relative;left:75%}.small-pull-9{position:relative;left:-75%}.small-offset-8{margin-left:66.6666666667%}.small-10{width:83.3333333333%}.small-push-10{position:relative;left:83.3333333333%}.small-pull-10{position:relative;left:-83.3333333333%}.small-offset-9{margin-left:75%}.small-11{width:91.6666666667%}.small-push-11{position:relative;left:91.6666666667%}.small-pull-11{position:relative;left:-91.6666666667%}.small-offset-10{margin-left:83.3333333333%}.small-12{width:100%}.small-offset-11{margin-left:91.6666666667%}.small-up-1>.column,.small-up-1>.columns{float:left;width:100%}.small-up-1>.column:nth-of-type(1n),.small-up-1>.columns:nth-of-type(1n){clear:none}.small-up-1>.column:nth-of-type(1n+1),.small-up-1>.columns:nth-of-type(1n+1){clear:both}.small-up-1>.column:last-child,.small-up-1>.columns:last-child{float:left}.small-up-2>.column,.small-up-2>.columns{float:left;width:50%}.small-up-2>.column:nth-of-type(1n),.small-up-2>.columns:nth-of-type(1n){clear:none}.small-up-2>.column:nth-of-type(2n+1),.small-up-2>.columns:nth-of-type(2n+1){clear:both}.small-up-2>.column:last-child,.small-up-2>.columns:last-child{float:left}.small-up-3>.column,.small-up-3>.columns{float:left;width:33.3333333333%}.small-up-3>.column:nth-of-type(1n),.small-up-3>.columns:nth-of-type(1n){clear:none}.small-up-3>.column:nth-of-type(3n+1),.small-up-3>.columns:nth-of-type(3n+1){clear:both}.small-up-3>.column:last-child,.small-up-3>.columns:last-child{float:left}.small-up-4>.column,.small-up-4>.columns{float:left;width:25%}.small-up-4>.column:nth-of-type(1n),.small-up-4>.columns:nth-of-type(1n){clear:none}.small-up-4>.column:nth-of-type(4n+1),.small-up-4>.columns:nth-of-type(4n+1){clear:both}.small-up-4>.column:last-child,.small-up-4>.columns:last-child{float:left}.small-up-5>.column,.small-up-5>.columns{float:left;width:20%}.small-up-5>.column:nth-of-type(1n),.small-up-5>.columns:nth-of-type(1n){clear:none}.small-up-5>.column:nth-of-type(5n+1),.small-up-5>.columns:nth-of-type(5n+1){clear:both}.small-up-5>.column:last-child,.small-up-5>.columns:last-child{float:left}.small-up-6>.column,.small-up-6>.columns{float:left;width:16.6666666667%}.small-up-6>.column:nth-of-type(1n),.small-up-6>.columns:nth-of-type(1n){clear:none}.small-up-6>.column:nth-of-type(6n+1),.small-up-6>.columns:nth-of-type(6n+1){clear:both}.small-up-6>.column:last-child,.small-up-6>.columns:last-child{float:left}.small-up-7>.column,.small-up-7>.columns{float:left;width:14.2857142857%}.small-up-7>.column:nth-of-type(1n),.small-up-7>.columns:nth-of-type(1n){clear:none}.small-up-7>.column:nth-of-type(7n+1),.small-up-7>.columns:nth-of-type(7n+1){clear:both}.small-up-7>.column:last-child,.small-up-7>.columns:last-child{float:left}.small-up-8>.column,.small-up-8>.columns{float:left;width:12.5%}.small-up-8>.column:nth-of-type(1n),.small-up-8>.columns:nth-of-type(1n){clear:none}.small-up-8>.column:nth-of-type(8n+1),.small-up-8>.columns:nth-of-type(8n+1){clear:both}.small-up-8>.column:last-child,.small-up-8>.columns:last-child{float:left}.small-collapse>.column,.small-collapse>.columns{padding-right:0;padding-left:0}.small-collapse .row{margin-right:0;margin-left:0}.expanded.row .small-collapse.row{margin-right:0;margin-left:0}.small-uncollapse>.column,.small-uncollapse>.columns{padding-right:.6578947368rem;padding-left:.6578947368rem}.small-centered{margin-right:auto;margin-left:auto}.small-centered,.small-centered:last-child:not(:first-child){float:none;clear:both}.small-pull-0,.small-push-0,.small-uncentered{position:static;float:left;margin-right:0;margin-left:0}@media print,screen and (min-width:40em){.medium-1{width:8.3333333333%}.medium-push-1{position:relative;left:8.3333333333%}.medium-pull-1{position:relative;left:-8.3333333333%}.medium-offset-0{margin-left:0}.medium-2{width:16.6666666667%}.medium-push-2{position:relative;left:16.6666666667%}.medium-pull-2{position:relative;left:-16.6666666667%}.medium-offset-1{margin-left:8.3333333333%}.medium-3{width:25%}.medium-push-3{position:relative;left:25%}.medium-pull-3{position:relative;left:-25%}.medium-offset-2{margin-left:16.6666666667%}.medium-4{width:33.3333333333%}.medium-push-4{position:relative;left:33.3333333333%}.medium-pull-4{position:relative;left:-33.3333333333%}.medium-offset-3{margin-left:25%}.medium-5{width:41.6666666667%}.medium-push-5{position:relative;left:41.6666666667%}.medium-pull-5{position:relative;left:-41.6666666667%}.medium-offset-4{margin-left:33.3333333333%}.medium-6{width:50%}.medium-push-6{position:relative;left:50%}.medium-pull-6{position:relative;left:-50%}.medium-offset-5{margin-left:41.6666666667%}.medium-7{width:58.3333333333%}.medium-push-7{position:relative;left:58.3333333333%}.medium-pull-7{position:relative;left:-58.3333333333%}.medium-offset-6{margin-left:50%}.medium-8{width:66.6666666667%}.medium-push-8{position:relative;left:66.6666666667%}.medium-pull-8{position:relative;left:-66.6666666667%}.medium-offset-7{margin-left:58.3333333333%}.medium-9{width:75%}.medium-push-9{position:relative;left:75%}.medium-pull-9{position:relative;left:-75%}.medium-offset-8{margin-left:66.6666666667%}.medium-10{width:83.3333333333%}.medium-push-10{position:relative;left:83.3333333333%}.medium-pull-10{position:relative;left:-83.3333333333%}.medium-offset-9{margin-left:75%}.medium-11{width:91.6666666667%}.medium-push-11{position:relative;left:91.6666666667%}.medium-pull-11{position:relative;left:-91.6666666667%}.medium-offset-10{margin-left:83.3333333333%}.medium-12{width:100%}.medium-offset-11{margin-left:91.6666666667%}.medium-up-1>.column,.medium-up-1>.columns{float:left;width:100%}.medium-up-1>.column:nth-of-type(1n),.medium-up-1>.columns:nth-of-type(1n){clear:none}.medium-up-1>.column:nth-of-type(1n+1),.medium-up-1>.columns:nth-of-type(1n+1){clear:both}.medium-up-1>.column:last-child,.medium-up-1>.columns:last-child{float:left}.medium-up-2>.column,.medium-up-2>.columns{float:left;width:50%}.medium-up-2>.column:nth-of-type(1n),.medium-up-2>.columns:nth-of-type(1n){clear:none}.medium-up-2>.column:nth-of-type(2n+1),.medium-up-2>.columns:nth-of-type(2n+1){clear:both}.medium-up-2>.column:last-child,.medium-up-2>.columns:last-child{float:left}.medium-up-3>.column,.medium-up-3>.columns{float:left;width:33.3333333333%}.medium-up-3>.column:nth-of-type(1n),.medium-up-3>.columns:nth-of-type(1n){clear:none}.medium-up-3>.column:nth-of-type(3n+1),.medium-up-3>.columns:nth-of-type(3n+1){clear:both}.medium-up-3>.column:last-child,.medium-up-3>.columns:last-child{float:left}.medium-up-4>.column,.medium-up-4>.columns{float:left;width:25%}.medium-up-4>.column:nth-of-type(1n),.medium-up-4>.columns:nth-of-type(1n){clear:none}.medium-up-4>.column:nth-of-type(4n+1),.medium-up-4>.columns:nth-of-type(4n+1){clear:both}.medium-up-4>.column:last-child,.medium-up-4>.columns:last-child{float:left}.medium-up-5>.column,.medium-up-5>.columns{float:left;width:20%}.medium-up-5>.column:nth-of-type(1n),.medium-up-5>.columns:nth-of-type(1n){clear:none}.medium-up-5>.column:nth-of-type(5n+1),.medium-up-5>.columns:nth-of-type(5n+1){clear:both}.medium-up-5>.column:last-child,.medium-up-5>.columns:last-child{float:left}.medium-up-6>.column,.medium-up-6>.columns{float:left;width:16.6666666667%}.medium-up-6>.column:nth-of-type(1n),.medium-up-6>.columns:nth-of-type(1n){clear:none}.medium-up-6>.column:nth-of-type(6n+1),.medium-up-6>.columns:nth-of-type(6n+1){clear:both}.medium-up-6>.column:last-child,.medium-up-6>.columns:last-child{float:left}.medium-up-7>.column,.medium-up-7>.columns{float:left;width:14.2857142857%}.medium-up-7>.column:nth-of-type(1n),.medium-up-7>.columns:nth-of-type(1n){clear:none}.medium-up-7>.column:nth-of-type(7n+1),.medium-up-7>.columns:nth-of-type(7n+1){clear:both}.medium-up-7>.column:last-child,.medium-up-7>.columns:last-child{float:left}.medium-up-8>.column,.medium-up-8>.columns{float:left;width:12.5%}.medium-up-8>.column:nth-of-type(1n),.medium-up-8>.columns:nth-of-type(1n){clear:none}.medium-up-8>.column:nth-of-type(8n+1),.medium-up-8>.columns:nth-of-type(8n+1){clear:both}.medium-up-8>.column:last-child,.medium-up-8>.columns:last-child{float:left}.medium-collapse>.column,.medium-collapse>.columns{padding-right:0;padding-left:0}.medium-collapse .row{margin-right:0;margin-left:0}.expanded.row .medium-collapse.row{margin-right:0;margin-left:0}.medium-uncollapse>.column,.medium-uncollapse>.columns{padding-right:.9868421053rem;padding-left:.9868421053rem}.medium-centered{margin-right:auto;margin-left:auto}.medium-centered,.medium-centered:last-child:not(:first-child){float:none;clear:both}.medium-pull-0,.medium-push-0,.medium-uncentered{position:static;float:left;margin-right:0;margin-left:0}}@media print,screen and (min-width:64em){.large-1{width:8.3333333333%}.large-push-1{position:relative;left:8.3333333333%}.large-pull-1{position:relative;left:-8.3333333333%}.large-offset-0{margin-left:0}.large-2{width:16.6666666667%}.large-push-2{position:relative;left:16.6666666667%}.large-pull-2{position:relative;left:-16.6666666667%}.large-offset-1{margin-left:8.3333333333%}.large-3{width:25%}.large-push-3{position:relative;left:25%}.large-pull-3{position:relative;left:-25%}.large-offset-2{margin-left:16.6666666667%}.large-4{width:33.3333333333%}.large-push-4{position:relative;left:33.3333333333%}.large-pull-4{position:relative;left:-33.3333333333%}.large-offset-3{margin-left:25%}.large-5{width:41.6666666667%}.large-push-5{position:relative;left:41.6666666667%}.large-pull-5{position:relative;left:-41.6666666667%}.large-offset-4{margin-left:33.3333333333%}.large-6{width:50%}.large-push-6{position:relative;left:50%}.large-pull-6{position:relative;left:-50%}.large-offset-5{margin-left:41.6666666667%}.large-7{width:58.3333333333%}.large-push-7{position:relative;left:58.3333333333%}.large-pull-7{position:relative;left:-58.3333333333%}.large-offset-6{margin-left:50%}.large-8{width:66.6666666667%}.large-push-8{position:relative;left:66.6666666667%}.large-pull-8{position:relative;left:-66.6666666667%}.large-offset-7{margin-left:58.3333333333%}.large-9{width:75%}.large-push-9{position:relative;left:75%}.large-pull-9{position:relative;left:-75%}.large-offset-8{margin-left:66.6666666667%}.large-10{width:83.3333333333%}.large-push-10{position:relative;left:83.3333333333%}.large-pull-10{position:relative;left:-83.3333333333%}.large-offset-9{margin-left:75%}.large-11{width:91.6666666667%}.large-push-11{position:relative;left:91.6666666667%}.large-pull-11{position:relative;left:-91.6666666667%}.large-offset-10{margin-left:83.3333333333%}.large-12{width:100%}.large-offset-11{margin-left:91.6666666667%}.large-up-1>.column,.large-up-1>.columns{float:left;width:100%}.large-up-1>.column:nth-of-type(1n),.large-up-1>.columns:nth-of-type(1n){clear:none}.large-up-1>.column:nth-of-type(1n+1),.large-up-1>.columns:nth-of-type(1n+1){clear:both}.large-up-1>.column:last-child,.large-up-1>.columns:last-child{float:left}.large-up-2>.column,.large-up-2>.columns{float:left;width:50%}.large-up-2>.column:nth-of-type(1n),.large-up-2>.columns:nth-of-type(1n){clear:none}.large-up-2>.column:nth-of-type(2n+1),.large-up-2>.columns:nth-of-type(2n+1){clear:both}.large-up-2>.column:last-child,.large-up-2>.columns:last-child{float:left}.large-up-3>.column,.large-up-3>.columns{float:left;width:33.3333333333%}.large-up-3>.column:nth-of-type(1n),.large-up-3>.columns:nth-of-type(1n){clear:none}.large-up-3>.column:nth-of-type(3n+1),.large-up-3>.columns:nth-of-type(3n+1){clear:both}.large-up-3>.column:last-child,.large-up-3>.columns:last-child{float:left}.large-up-4>.column,.large-up-4>.columns{float:left;width:25%}.large-up-4>.column:nth-of-type(1n),.large-up-4>.columns:nth-of-type(1n){clear:none}.large-up-4>.column:nth-of-type(4n+1),.large-up-4>.columns:nth-of-type(4n+1){clear:both}.large-up-4>.column:last-child,.large-up-4>.columns:last-child{float:left}.large-up-5>.column,.large-up-5>.columns{float:left;width:20%}.large-up-5>.column:nth-of-type(1n),.large-up-5>.columns:nth-of-type(1n){clear:none}.large-up-5>.column:nth-of-type(5n+1),.large-up-5>.columns:nth-of-type(5n+1){clear:both}.large-up-5>.column:last-child,.large-up-5>.columns:last-child{float:left}.large-up-6>.column,.large-up-6>.columns{float:left;width:16.6666666667%}.large-up-6>.column:nth-of-type(1n),.large-up-6>.columns:nth-of-type(1n){clear:none}.large-up-6>.column:nth-of-type(6n+1),.large-up-6>.columns:nth-of-type(6n+1){clear:both}.large-up-6>.column:last-child,.large-up-6>.columns:last-child{float:left}.large-up-7>.column,.large-up-7>.columns{float:left;width:14.2857142857%}.large-up-7>.column:nth-of-type(1n),.large-up-7>.columns:nth-of-type(1n){clear:none}.large-up-7>.column:nth-of-type(7n+1),.large-up-7>.columns:nth-of-type(7n+1){clear:both}.large-up-7>.column:last-child,.large-up-7>.columns:last-child{float:left}.large-up-8>.column,.large-up-8>.columns{float:left;width:12.5%}.large-up-8>.column:nth-of-type(1n),.large-up-8>.columns:nth-of-type(1n){clear:none}.large-up-8>.column:nth-of-type(8n+1),.large-up-8>.columns:nth-of-type(8n+1){clear:both}.large-up-8>.column:last-child,.large-up-8>.columns:last-child{float:left}.large-collapse>.column,.large-collapse>.columns{padding-right:0;padding-left:0}.large-collapse .row{margin-right:0;margin-left:0}.expanded.row .large-collapse.row{margin-right:0;margin-left:0}.large-uncollapse>.column,.large-uncollapse>.columns{padding-right:.9868421053rem;padding-left:.9868421053rem}.large-centered{margin-right:auto;margin-left:auto}.large-centered,.large-centered:last-child:not(:first-child){float:none;clear:both}.large-pull-0,.large-push-0,.large-uncentered{position:static;float:left;margin-right:0;margin-left:0}}.column-block{margin-bottom:1.3157894737rem}.column-block>:last-child{margin-bottom:0}@media print,screen and (min-width:40em){.column-block{margin-bottom:1.9736842105rem}.column-block>:last-child{margin-bottom:0}}blockquote,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,li,ol,p,pre,td,th,ul{margin:0;padding:0}p{margin-bottom:1rem;font-size:inherit;line-height:1.6;text-rendering:optimizeLegibility}em,i{font-style:italic;line-height:inherit}b,strong{font-weight:700;line-height:inherit}small{font-size:80%;line-height:inherit}h1,h2,h3,h4,h5,h6{font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-style:normal;font-weight:400;color:inherit;text-rendering:optimizeLegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{line-height:0;color:#cacaca}h1{font-size:1.5789473684rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}h2{font-size:1.3157894737rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}h3{font-size:1.25rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}h4{font-size:1.1842105263rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}h5{font-size:1.1184210526rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}h6{font-size:1.0526315789rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}@media print,screen and (min-width:40em){h1{font-size:1.7105263158rem}h2{font-size:1.5789473684rem}h3{font-size:1.3157894737rem}h4{font-size:1.1842105263rem}h5{font-size:1.1184210526rem}h6{font-size:1.0526315789rem}}a{line-height:inherit;color:#1779ba;text-decoration:none;cursor:pointer}a:focus,a:hover{color:#1468a0}a img{border:0}hr{clear:both;max-width:auto;height:0;margin:1.3157894737rem auto;border-top:0;border-right:0;border-bottom:1px solid #cacaca;border-left:0}dl,ol,ul{margin-bottom:1rem;list-style-position:outside;line-height:1.6}li{font-size:inherit}ul{margin-left:1.25rem;list-style-type:disc}ol{margin-left:1.25rem}ol ol,ol ul,ul ol,ul ul{margin-left:1.25rem;margin-bottom:0}dl{margin-bottom:1rem}dl dt{margin-bottom:.3rem;font-weight:700}blockquote{margin:0 0 1rem;padding:.5921052632rem 1.3157894737rem 0 1.25rem;border-left:1px solid #cacaca}blockquote,blockquote p{line-height:1.6;color:#222}cite{display:block;font-size:.8552631579rem;color:#222}cite:before{content:"— "}abbr,abbr[title]{border-bottom:1px dotted #181818;cursor:help;text-decoration:none}figure{margin:0}code{padding:.1315789474rem .3289473684rem .0657894737rem;border:1px solid #cacaca;background-color:#e6e6e6;font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:400;color:#181818}kbd{margin:0;padding:.1315789474rem .2631578947rem 0;background-color:#e6e6e6;font-family:Consolas,"Liberation Mono",Courier,monospace;color:#181818}.subheader{margin-top:.2rem;margin-bottom:.5rem;font-weight:400;line-height:1.4;color:#222}.lead{font-size:118.75%;line-height:1.6}.stat{font-size:2.5rem;line-height:1}p+.stat{margin-top:-1rem}ol.no-bullet,ul.no-bullet{margin-left:0;list-style:none}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}@media print,screen and (min-width:40em){.medium-text-left{text-align:left}.medium-text-right{text-align:right}.medium-text-center{text-align:center}.medium-text-justify{text-align:justify}}@media print,screen and (min-width:64em){.large-text-left{text-align:left}.large-text-right{text-align:right}.large-text-center{text-align:center}.large-text-justify{text-align:justify}}.show-for-print{display:none!important}@media print{*{background:0 0!important;box-shadow:none!important;color:#000!important;text-shadow:none!important}.show-for-print{display:block!important}.hide-for-print{display:none!important}table.show-for-print{display:table!important}thead.show-for-print{display:table-header-group!important}tbody.show-for-print{display:table-row-group!important}tr.show-for-print{display:table-row!important}td.show-for-print{display:table-cell!important}th.show-for-print{display:table-cell!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}.ir a:after,a[href^='#']:after,a[href^='javascript:']:after{content:''}abbr[title]:after{content:" (" attr(title) ")"}blockquote,pre{border:1px solid #222;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.print-break-inside{page-break-inside:auto}}.button{display:inline-block;vertical-align:middle;margin:0 0 1rem 0;font-family:inherit;padding:.85em 1em;-webkit-appearance:none;border:1px solid transparent;border-radius:0;transition:background-color .25s ease-out,color .25s ease-out;font-size:.9rem;line-height:1;text-align:center;cursor:pointer;background-color:#1779ba;color:#fefefe}[data-whatinput=mouse] .button{outline:0}.button:focus,.button:hover{background-color:#14679e;color:#fefefe}.button.tiny{font-size:.6rem}.button.small{font-size:.75rem}.button.large{font-size:1.25rem}.button.expanded{display:block;width:100%;margin-right:0;margin-left:0}.button.primary{background-color:#1779ba;color:#fefefe}.button.primary:focus,.button.primary:hover{background-color:#126195;color:#fefefe}.button.secondary{background-color:#767676;color:#fefefe}.button.secondary:focus,.button.secondary:hover{background-color:#5e5e5e;color:#fefefe}.button.success{background-color:#3adb76;color:#181818}.button.success:focus,.button.success:hover{background-color:#22bb5b;color:#181818}.button.warning{background-color:#ffae00;color:#181818}.button.warning:focus,.button.warning:hover{background-color:#cc8b00;color:#181818}.button.alert{background-color:#cc4b37;color:#fefefe}.button.alert:focus,.button.alert:hover{background-color:#a53b2a;color:#fefefe}.button.disabled,.button[disabled]{opacity:.25;cursor:not-allowed}.button.disabled,.button.disabled:focus,.button.disabled:hover,.button[disabled],.button[disabled]:focus,.button[disabled]:hover{background-color:#1779ba;color:#fefefe}.button.disabled.primary,.button[disabled].primary{opacity:.25;cursor:not-allowed}.button.disabled.primary,.button.disabled.primary:focus,.button.disabled.primary:hover,.button[disabled].primary,.button[disabled].primary:focus,.button[disabled].primary:hover{background-color:#1779ba;color:#fefefe}.button.disabled.secondary,.button[disabled].secondary{opacity:.25;cursor:not-allowed}.button.disabled.secondary,.button.disabled.secondary:focus,.button.disabled.secondary:hover,.button[disabled].secondary,.button[disabled].secondary:focus,.button[disabled].secondary:hover{background-color:#767676;color:#fefefe}.button.disabled.success,.button[disabled].success{opacity:.25;cursor:not-allowed}.button.disabled.success,.button.disabled.success:focus,.button.disabled.success:hover,.button[disabled].success,.button[disabled].success:focus,.button[disabled].success:hover{background-color:#3adb76;color:#181818}.button.disabled.warning,.button[disabled].warning{opacity:.25;cursor:not-allowed}.button.disabled.warning,.button.disabled.warning:focus,.button.disabled.warning:hover,.button[disabled].warning,.button[disabled].warning:focus,.button[disabled].warning:hover{background-color:#ffae00;color:#181818}.button.disabled.alert,.button[disabled].alert{opacity:.25;cursor:not-allowed}.button.disabled.alert,.button.disabled.alert:focus,.button.disabled.alert:hover,.button[disabled].alert,.button[disabled].alert:focus,.button[disabled].alert:hover{background-color:#cc4b37;color:#fefefe}.button.hollow{border:1px solid #1779ba;color:#1779ba}.button.hollow,.button.hollow:focus,.button.hollow:hover{background-color:transparent}.button.hollow.disabled,.button.hollow.disabled:focus,.button.hollow.disabled:hover,.button.hollow[disabled],.button.hollow[disabled]:focus,.button.hollow[disabled]:hover{background-color:transparent}.button.hollow:focus,.button.hollow:hover{border-color:#0c3d5d;color:#0c3d5d}.button.hollow:focus.disabled,.button.hollow:focus[disabled],.button.hollow:hover.disabled,.button.hollow:hover[disabled]{border:1px solid #1779ba;color:#1779ba}.button.hollow.primary{border:1px solid #1779ba;color:#1779ba}.button.hollow.primary:focus,.button.hollow.primary:hover{border-color:#0c3d5d;color:#0c3d5d}.button.hollow.primary:focus.disabled,.button.hollow.primary:focus[disabled],.button.hollow.primary:hover.disabled,.button.hollow.primary:hover[disabled]{border:1px solid #1779ba;color:#1779ba}.button.hollow.secondary{border:1px solid #767676;color:#767676}.button.hollow.secondary:focus,.button.hollow.secondary:hover{border-color:#3b3b3b;color:#3b3b3b}.button.hollow.secondary:focus.disabled,.button.hollow.secondary:focus[disabled],.button.hollow.secondary:hover.disabled,.button.hollow.secondary:hover[disabled]{border:1px solid #767676;color:#767676}.button.hollow.success{border:1px solid #3adb76;color:#3adb76}.button.hollow.success:focus,.button.hollow.success:hover{border-color:#157539;color:#157539}.button.hollow.success:focus.disabled,.button.hollow.success:focus[disabled],.button.hollow.success:hover.disabled,.button.hollow.success:hover[disabled]{border:1px solid #3adb76;color:#3adb76}.button.hollow.warning{border:1px solid #ffae00;color:#ffae00}.button.hollow.warning:focus,.button.hollow.warning:hover{border-color:#805700;color:#805700}.button.hollow.warning:focus.disabled,.button.hollow.warning:focus[disabled],.button.hollow.warning:hover.disabled,.button.hollow.warning:hover[disabled]{border:1px solid #ffae00;color:#ffae00}.button.hollow.alert{border:1px solid #cc4b37;color:#cc4b37}.button.hollow.alert:focus,.button.hollow.alert:hover{border-color:#67251a;color:#67251a}.button.hollow.alert:focus.disabled,.button.hollow.alert:focus[disabled],.button.hollow.alert:hover.disabled,.button.hollow.alert:hover[disabled]{border:1px solid #cc4b37;color:#cc4b37}.button.clear{border:1px solid #1779ba;color:#1779ba}.button.clear,.button.clear:focus,.button.clear:hover{background-color:transparent}.button.clear.disabled,.button.clear.disabled:focus,.button.clear.disabled:hover,.button.clear[disabled],.button.clear[disabled]:focus,.button.clear[disabled]:hover{background-color:transparent}.button.clear:focus,.button.clear:hover{border-color:#0c3d5d;color:#0c3d5d}.button.clear:focus.disabled,.button.clear:focus[disabled],.button.clear:hover.disabled,.button.clear:hover[disabled]{border:1px solid #1779ba;color:#1779ba}.button.clear,.button.clear.disabled,.button.clear:focus,.button.clear:focus.disabled,.button.clear:focus[disabled],.button.clear:hover,.button.clear:hover.disabled,.button.clear:hover[disabled],.button.clear[disabled]{border-color:transparent}.button.clear.primary{border:1px solid #1779ba;color:#1779ba}.button.clear.primary:focus,.button.clear.primary:hover{border-color:#0c3d5d;color:#0c3d5d}.button.clear.primary:focus.disabled,.button.clear.primary:focus[disabled],.button.clear.primary:hover.disabled,.button.clear.primary:hover[disabled]{border:1px solid #1779ba;color:#1779ba}.button.clear.primary,.button.clear.primary.disabled,.button.clear.primary:focus,.button.clear.primary:focus.disabled,.button.clear.primary:focus[disabled],.button.clear.primary:hover,.button.clear.primary:hover.disabled,.button.clear.primary:hover[disabled],.button.clear.primary[disabled]{border-color:transparent}.button.clear.secondary{border:1px solid #767676;color:#767676}.button.clear.secondary:focus,.button.clear.secondary:hover{border-color:#3b3b3b;color:#3b3b3b}.button.clear.secondary:focus.disabled,.button.clear.secondary:focus[disabled],.button.clear.secondary:hover.disabled,.button.clear.secondary:hover[disabled]{border:1px solid #767676;color:#767676}.button.clear.secondary,.button.clear.secondary.disabled,.button.clear.secondary:focus,.button.clear.secondary:focus.disabled,.button.clear.secondary:focus[disabled],.button.clear.secondary:hover,.button.clear.secondary:hover.disabled,.button.clear.secondary:hover[disabled],.button.clear.secondary[disabled]{border-color:transparent}.button.clear.success{border:1px solid #3adb76;color:#3adb76}.button.clear.success:focus,.button.clear.success:hover{border-color:#157539;color:#157539}.button.clear.success:focus.disabled,.button.clear.success:focus[disabled],.button.clear.success:hover.disabled,.button.clear.success:hover[disabled]{border:1px solid #3adb76;color:#3adb76}.button.clear.success,.button.clear.success.disabled,.button.clear.success:focus,.button.clear.success:focus.disabled,.button.clear.success:focus[disabled],.button.clear.success:hover,.button.clear.success:hover.disabled,.button.clear.success:hover[disabled],.button.clear.success[disabled]{border-color:transparent}.button.clear.warning{border:1px solid #ffae00;color:#ffae00}.button.clear.warning:focus,.button.clear.warning:hover{border-color:#805700;color:#805700}.button.clear.warning:focus.disabled,.button.clear.warning:focus[disabled],.button.clear.warning:hover.disabled,.button.clear.warning:hover[disabled]{border:1px solid #ffae00;color:#ffae00}.button.clear.warning,.button.clear.warning.disabled,.button.clear.warning:focus,.button.clear.warning:focus.disabled,.button.clear.warning:focus[disabled],.button.clear.warning:hover,.button.clear.warning:hover.disabled,.button.clear.warning:hover[disabled],.button.clear.warning[disabled]{border-color:transparent}.button.clear.alert{border:1px solid #cc4b37;color:#cc4b37}.button.clear.alert:focus,.button.clear.alert:hover{border-color:#67251a;color:#67251a}.button.clear.alert:focus.disabled,.button.clear.alert:focus[disabled],.button.clear.alert:hover.disabled,.button.clear.alert:hover[disabled]{border:1px solid #cc4b37;color:#cc4b37}.button.clear.alert,.button.clear.alert.disabled,.button.clear.alert:focus,.button.clear.alert:focus.disabled,.button.clear.alert:focus[disabled],.button.clear.alert:hover,.button.clear.alert:hover.disabled,.button.clear.alert:hover[disabled],.button.clear.alert[disabled]{border-color:transparent}.button.dropdown::after{display:block;width:0;height:0;border:inset .4em;content:'';border-bottom-width:0;border-top-style:solid;border-color:#fefefe transparent transparent;position:relative;top:.4em;display:inline-block;float:right;margin-left:1em}.button.dropdown.hollow::after{border-top-color:#1779ba}.button.dropdown.hollow.primary::after{border-top-color:#1779ba}.button.dropdown.hollow.secondary::after{border-top-color:#767676}.button.dropdown.hollow.success::after{border-top-color:#3adb76}.button.dropdown.hollow.warning::after{border-top-color:#ffae00}.button.dropdown.hollow.alert::after{border-top-color:#cc4b37}.button.arrow-only::after{top:-.1em;float:none;margin-left:0}[type=color],[type=date],[type=datetime-local],[type=datetime],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],textarea{display:block;box-sizing:border-box;width:100%;height:2.5657894737rem;margin:0 0 1.0526315789rem;padding:.5263157895rem;border:1px solid #cacaca;border-radius:0;background-color:#fefefe;box-shadow:inset 0 1px 2px rgba(24,24,24,.1);font-family:inherit;font-size:1.0526315789rem;font-weight:400;line-height:1.5;color:#181818;transition:box-shadow .5s,border-color .25s ease-in-out;appearance:none}[type=color]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=datetime]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,textarea:focus{outline:0;border:1px solid #222;background-color:#fefefe;box-shadow:0 0 5px #cacaca;transition:box-shadow .5s,border-color .25s ease-in-out}textarea{max-width:100%}textarea[rows]{height:auto}input::placeholder,textarea::placeholder{color:#cacaca}input:disabled,input[readonly],textarea:disabled,textarea[readonly]{background-color:#e6e6e6;cursor:not-allowed}[type=button],[type=submit]{appearance:none;border-radius:0}input[type=search]{box-sizing:border-box}[type=checkbox],[type=file],[type=radio]{margin:0 0 1.0526315789rem}[type=checkbox]+label,[type=radio]+label{display:inline-block;vertical-align:baseline;margin-left:.5263157895rem;margin-right:1.0526315789rem;margin-bottom:0}[type=checkbox]+label[for],[type=radio]+label[for]{cursor:pointer}label>[type=checkbox],label>[type=radio]{margin-right:.5263157895rem}[type=file]{width:100%}label{display:block;margin:0;font-size:.9210526316rem;font-weight:400;line-height:1.8;color:#181818}label.middle{margin:0 0 1.0526315789rem;padding:.5921052632rem 0}.help-text{margin-top:-.5263157895rem;font-size:.8552631579rem;font-style:italic;color:#181818}.input-group{display:flex;width:100%;margin-bottom:1.0526315789rem;align-items:stretch}.input-group>:first-child{border-radius:0}.input-group>:last-child>*{border-radius:0}.input-group-button,.input-group-button a,.input-group-button button,.input-group-button input,.input-group-button label,.input-group-field,.input-group-label{margin:0;white-space:nowrap}.input-group-label{padding:0 1rem;border:1px solid #cacaca;background:#e6e6e6;color:#181818;text-align:center;white-space:nowrap;display:flex;flex:0 0 auto;align-items:center}.input-group-label:first-child{border-right:0}.input-group-label:last-child{border-left:0}.input-group-field{border-radius:0;flex:1 1 0px;height:auto;min-width:0}.input-group-button{padding-top:0;padding-bottom:0;text-align:center;flex:0 0 auto}.input-group-button a,.input-group-button button,.input-group-button input,.input-group-button label{height:2.6315789474rem;padding-top:0;padding-bottom:0;font-size:1.0526315789rem}fieldset{margin:0;padding:0;border:0}legend{max-width:100%;margin-bottom:.5263157895rem}.fieldset{margin:1.1842105263rem 0;padding:1.3157894737rem;border:1px solid #cacaca}.fieldset legend{margin:0;margin-left:-.1973684211rem;padding:0 .1973684211rem}select{height:2.5657894737rem;margin:0 0 1.0526315789rem;padding:.5263157895rem;appearance:none;border:1px solid #cacaca;border-radius:0;background-color:#fefefe;font-family:inherit;font-size:1.0526315789rem;font-weight:400;line-height:1.5;color:#181818;background-image:url("data:image/svg+xml;utf8,");background-origin:content-box;background-position:right -1.0526315789rem center;background-repeat:no-repeat;background-size:9px 6px;padding-right:1.5789473684rem;transition:box-shadow .5s,border-color .25s ease-in-out}@media screen and (min-width:0\0){select{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg==)}}select:focus{outline:0;border:1px solid #222;background-color:#fefefe;box-shadow:0 0 5px #cacaca;transition:box-shadow .5s,border-color .25s ease-in-out}select:disabled{background-color:#e6e6e6;cursor:not-allowed}select::-ms-expand{display:none}select[multiple]{height:auto;background-image:none}.is-invalid-input:not(:focus){border-color:#cc4b37;background-color:#f9ecea}.is-invalid-input:not(:focus)::placeholder{color:#cc4b37}.is-invalid-label{color:#cc4b37}.form-error{display:none;margin-top:-.5263157895rem;margin-bottom:1.0526315789rem;font-size:.7894736842rem;font-weight:700;color:#cc4b37}.form-error.is-visible{display:block}.hide{display:none!important}.invisible{visibility:hidden}@media screen and (max-width:39.9375em){.hide-for-small-only{display:none!important}}@media screen and (max-width:0em),screen and (min-width:40em){.show-for-small-only{display:none!important}}@media print,screen and (min-width:40em){.hide-for-medium{display:none!important}}@media screen and (max-width:39.9375em){.show-for-medium{display:none!important}}@media screen and (min-width:40em) and (max-width:63.9375em){.hide-for-medium-only{display:none!important}}@media screen and (max-width:39.9375em),screen and (min-width:64em){.show-for-medium-only{display:none!important}}@media print,screen and (min-width:64em){.hide-for-large{display:none!important}}@media screen and (max-width:63.9375em){.show-for-large{display:none!important}}@media screen and (min-width:64em) and (max-width:74.9375em){.hide-for-large-only{display:none!important}}@media screen and (max-width:63.9375em),screen and (min-width:75em){.show-for-large-only{display:none!important}}.show-for-sr,.show-on-focus{position:absolute!important;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;clip-path:inset(50%);border:0}.show-on-focus:active,.show-on-focus:focus{position:static!important;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;clip-path:none}.hide-for-portrait,.show-for-landscape{display:block!important}@media screen and (orientation:landscape){.hide-for-portrait,.show-for-landscape{display:block!important}}@media screen and (orientation:portrait){.hide-for-portrait,.show-for-landscape{display:none!important}}.hide-for-landscape,.show-for-portrait{display:none!important}@media screen and (orientation:landscape){.hide-for-landscape,.show-for-portrait{display:none!important}}@media screen and (orientation:portrait){.hide-for-landscape,.show-for-portrait{display:block!important}}.float-left{float:left!important}.float-right{float:right!important}.float-center{display:block;margin-right:auto;margin-left:auto}.clearfix::after,.clearfix::before{display:table;content:' ';flex-basis:0;order:1}.clearfix::after{clear:both}.close-button{position:absolute;color:#222;cursor:pointer}[data-whatinput=mouse] .close-button{outline:0}.close-button:focus,.close-button:hover{color:#181818}.close-button.small{right:.66rem;top:.33em;font-size:1.5em;line-height:1}.close-button,.close-button.medium{right:1rem;top:.5rem;font-size:2em;line-height:1}.label{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#1779ba;color:#fefefe}.label.primary{background:#1779ba;color:#fefefe}.label.secondary{background:#767676;color:#fefefe}.label.success{background:#3adb76;color:#181818}.label.warning{background:#ffae00;color:#181818}.label.alert{background:#cc4b37;color:#fefefe}table{border-collapse:collapse;width:100%;margin-bottom:1rem;border-radius:0}tbody,tfoot,thead{border:1px solid #f1f1f1;background-color:#fefefe}caption{padding:.5263157895rem .6578947368rem .6578947368rem;font-weight:700}thead{background:#f8f8f8;color:#181818}tfoot{background:#f1f1f1;color:#181818}tfoot tr,thead tr{background:0 0}tfoot td,tfoot th,thead td,thead th{padding:.5263157895rem .6578947368rem .6578947368rem;font-weight:700;text-align:left}tbody td,tbody th{padding:.5263157895rem .6578947368rem .6578947368rem}tbody tr:nth-child(even){border-bottom:0;background-color:#f1f1f1}table.unstriped tbody{background-color:#fefefe}table.unstriped tbody tr{border-bottom:0;border-bottom:1px solid #f1f1f1;background-color:#fefefe}@media screen and (max-width:63.9375em){table.stack thead{display:none}table.stack tfoot{display:none}table.stack td,table.stack th,table.stack tr{display:block}table.stack td{border-top:0}}table.scroll{display:block;width:100%;overflow-x:auto}table.hover thead tr:hover{background-color:#f3f3f3}table.hover tfoot tr:hover{background-color:#ececec}table.hover tbody tr:hover{background-color:#f9f9f9}table.hover:not(.unstriped) tr:nth-of-type(even):hover{background-color:#ececec}.table-scroll{overflow-x:auto}.table-scroll table{width:auto} \ No newline at end of file diff --git a/ref/stylesheets/spectacle.min.css b/ref/stylesheets/spectacle.min.css new file mode 100644 index 0000000..95361d7 --- /dev/null +++ b/ref/stylesheets/spectacle.min.css @@ -0,0 +1 @@ +@charset "UTF-8";#spectacle .swagger-operation-path:before{content:"Path";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .swagger-operation-description:before{content:"Description";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .swagger-request-params:before{content:"Request parameters";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .swagger-request-body:before{content:"Request body";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .swagger-request-body .json-schema-properties:before{display:none}#spectacle .swagger-responses:before{content:"Responses";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .swagger-global:before{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#cc4b37;color:#fefefe;font-size:.75rem;border-radius:4px;padding:3px 6px;content:"global"}#spectacle table.table th.swagger-param-key{width:auto}#spectacle table.table th.swagger-param-key:before{content:"Key"}#spectacle table.table th.swagger-param-name{width:auto}#spectacle table.table th.swagger-param-name:before{content:"Name"}#spectacle table.table th.swagger-param-description{width:auto}#spectacle table.table th.swagger-param-description:before{content:"Description"}#spectacle table.table th.swagger-param-data-type{width:auto}#spectacle table.table th.swagger-param-data-type:before{content:"Data type"}#spectacle table.table th.swagger-param-type{width:auto}#spectacle table.table th.swagger-param-type:before{content:"Type"}#spectacle table.table th.swagger-request-security-schema{width:auto}#spectacle table.table th.swagger-request-security-schema:before{content:"Schema"}#spectacle table.table th.swagger-request-security-scopes{width:auto}#spectacle table.table th.swagger-request-security-scopes:before{content:"Scopes"}#spectacle table.table th.swagger-response-header-name{width:auto}#spectacle table.table th.swagger-response-header-name:before{content:"Header"}#spectacle table.table th.swagger-response-header-description{width:auto}#spectacle table.table th.swagger-response-header-description:before{content:"Description"}#spectacle table.table th.swagger-response-header-data-type{width:auto}#spectacle table.table th.swagger-response-header-data-type:before{content:"Data type"}#spectacle table.table th.swagger-response-code{width:auto}#spectacle table.table th.swagger-response-code:before{content:"Code"}#spectacle table.table th.swagger-response-description{width:auto}#spectacle table.table th.swagger-response-description:before{content:"Description"}#spectacle table.table th.swagger-response-schema{width:auto}#spectacle table.table th.swagger-response-schema:before{content:"Schema"}#spectacle .swagger-response-name-value{font-weight:700}#spectacle .swagger-response-description-text{padding-bottom:.5em}#spectacle .swagger-request-security:before{content:"Security";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .swagger-security-definition-basic:before{color:#cacaca;content:"(HTTP Basic Authentication)"}#spectacle .swagger-security-definition-oauth2:before{color:#cacaca;content:"(OAuth2 Authentication)"}#spectacle .swagger-security-definition-apiKey:before{color:#cacaca;content:"(API Key Authentication)"}#spectacle .json-schema-description:before{content:"Description";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .json-schema-properties:before{content:"Properties";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .json-schema-properties dl{margin:0}#spectacle .json-schema-properties dt{margin-bottom:.25rem}#spectacle .json-schema-properties dt.has-description{margin-bottom:0}#spectacle .json-schema-properties dd{color:#222}#spectacle .json-schema-properties dd:not(:last-child){padding-bottom:.5rem}#spectacle .json-schema-description+.json-schema-properties{margin-top:1.5rem}#spectacle .json-schema-ref-array:before{color:#222;content:"Array<"}#spectacle .json-schema-ref-array:after{color:#222;content:">"}#spectacle .json-schema-example:before{content:"Example";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .json-schema-array-items:before{content:"Items";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .json-schema-allOf-inherited:before{content:"Inherited";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .json-schema-anyOf>dl{padding-left:1em}#spectacle .json-schema-anyOf>dl dt:not(:first-child):before{content:"or "}#spectacle .json-schema-anyOf>dl dt:first-child:before{content:"either "}#spectacle .json-schema-additionalProperties:before{content:"Additional properties";display:block;margin-bottom:.5em;color:#f68b1f;text-transform:uppercase;font-size:.9rem}#spectacle .json-inner-schema .json-schema-array-items,#spectacle .json-inner-schema .json-schema-description,#spectacle .json-inner-schema .json-schema-example,#spectacle .json-inner-schema .json-schema-properties{padding-left:1em;margin-top:.5em;padding-bottom:.5em}#spectacle .json-property-discriminator:before{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#cc4b37;color:#fefefe;font-size:.75rem;border-radius:4px;padding:3px 6px;content:"discriminator"}#spectacle .json-property-required:before{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#cc4b37;color:#fefefe;font-size:.75rem;border-radius:4px;padding:3px 6px;content:"required"}#spectacle .json-property-read-only:before{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#cc4b37;color:#fefefe;font-size:.75rem;border-radius:4px;padding:3px 6px;content:"read only"}#spectacle .json-property-format{font-style:italic}#spectacle .json-property-enum:before{content:'enum: ['}#spectacle .json-property-enum:after{content:']'}#spectacle .json-property-default-value:before{content:'default: '}#spectacle .json-property-range:empty{display:none!important}#spectacle .json-property-range:before{content:'range: '}#spectacle .json-property-enum-item{font-weight:lighter;font-size:small}#spectacle .json-schema-reference{font-size:90%}#spectacle .no-padding{padding:0!important}#spectacle .no-margin{margin:0!important}#spectacle button:focus{outline:0}#spectacle .default-label{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#767676;color:#fefefe;font-size:.75rem;border-radius:4px;padding:3px 6px}#spectacle #logo{text-align:left;padding-right:.5rem;padding-top:1rem;padding-bottom:.25rem}#spectacle #logo img{max-height:75px}#spectacle .row,#spectacle article .doc-row,#spectacle article .prop-row{max-width:auto;margin-right:auto;margin-left:auto}#spectacle .row::after,#spectacle .row::before,#spectacle article .doc-row::after,#spectacle article .doc-row::before,#spectacle article .prop-row::after,#spectacle article .prop-row::before{display:table;content:' ';flex-basis:0;order:1}#spectacle .row::after,#spectacle article .doc-row::after,#spectacle article .prop-row::after{clear:both}#spectacle .row.collapse>.column,#spectacle .row.collapse>.columns,#spectacle article .doc-row>.column,#spectacle article .doc-row>.columns,#spectacle article .doc-row>.doc-copy,#spectacle article .doc-row>.doc-examples,#spectacle article .prop-row .doc-row>.prop-name,#spectacle article .prop-row .doc-row>.prop-value,#spectacle article .prop-row .row.collapse>.prop-name,#spectacle article .prop-row .row.collapse>.prop-value,#spectacle article .prop-row>.column,#spectacle article .prop-row>.columns,#spectacle article .prop-row>.doc-copy,#spectacle article .prop-row>.doc-examples,#spectacle article .prop-row>.prop-name,#spectacle article .prop-row>.prop-value,#spectacle article .row.collapse>.doc-copy,#spectacle article .row.collapse>.doc-examples{padding-right:0;padding-left:0}#spectacle .row .row,#spectacle .row article .doc-row,#spectacle .row article .prop-row,#spectacle article .doc-row .doc-row,#spectacle article .doc-row .prop-row,#spectacle article .doc-row .row,#spectacle article .prop-row .doc-row,#spectacle article .prop-row .prop-row,#spectacle article .prop-row .row,#spectacle article .row .doc-row,#spectacle article .row .prop-row{margin-right:-.6578947368rem;margin-left:-.6578947368rem}@media print,screen and (min-width:40em){#spectacle .row .row,#spectacle .row article .doc-row,#spectacle .row article .prop-row,#spectacle article .doc-row .doc-row,#spectacle article .doc-row .prop-row,#spectacle article .doc-row .row,#spectacle article .prop-row .doc-row,#spectacle article .prop-row .prop-row,#spectacle article .prop-row .row,#spectacle article .row .doc-row,#spectacle article .row .prop-row{margin-right:-.9868421053rem;margin-left:-.9868421053rem}}@media print,screen and (min-width:64em){#spectacle .row .row,#spectacle .row article .doc-row,#spectacle .row article .prop-row,#spectacle article .doc-row .doc-row,#spectacle article .doc-row .prop-row,#spectacle article .doc-row .row,#spectacle article .prop-row .doc-row,#spectacle article .prop-row .prop-row,#spectacle article .prop-row .row,#spectacle article .row .doc-row,#spectacle article .row .prop-row{margin-right:-.9868421053rem;margin-left:-.9868421053rem}}#spectacle .row .row.collapse,#spectacle .row article .doc-row,#spectacle .row article .prop-row,#spectacle article .doc-row .doc-row,#spectacle article .doc-row .prop-row,#spectacle article .doc-row .row.collapse,#spectacle article .prop-row .doc-row,#spectacle article .prop-row .prop-row,#spectacle article .prop-row .row.collapse,#spectacle article .row .doc-row,#spectacle article .row .prop-row{margin-right:0;margin-left:0}#spectacle .row.expanded,#spectacle article .expanded.doc-row,#spectacle article .expanded.prop-row{max-width:none}#spectacle .row.expanded .row,#spectacle .row.expanded article .doc-row,#spectacle .row.expanded article .prop-row,#spectacle article .expanded.doc-row .doc-row,#spectacle article .expanded.doc-row .prop-row,#spectacle article .expanded.doc-row .row,#spectacle article .expanded.prop-row .doc-row,#spectacle article .expanded.prop-row .prop-row,#spectacle article .expanded.prop-row .row,#spectacle article .row.expanded .doc-row,#spectacle article .row.expanded .prop-row{margin-right:auto;margin-left:auto}#spectacle .row:not(.expanded) .row,#spectacle .row:not(.expanded) article .doc-row,#spectacle .row:not(.expanded) article .prop-row,#spectacle article .doc-row:not(.expanded) .doc-row,#spectacle article .doc-row:not(.expanded) .prop-row,#spectacle article .doc-row:not(.expanded) .row,#spectacle article .prop-row:not(.expanded) .doc-row,#spectacle article .prop-row:not(.expanded) .prop-row,#spectacle article .prop-row:not(.expanded) .row,#spectacle article .row:not(.expanded) .doc-row,#spectacle article .row:not(.expanded) .prop-row{max-width:none}#spectacle .row.gutter-small>.column,#spectacle .row.gutter-small>.columns,#spectacle article .gutter-small.doc-row>.column,#spectacle article .gutter-small.doc-row>.columns,#spectacle article .gutter-small.doc-row>.doc-copy,#spectacle article .gutter-small.doc-row>.doc-examples,#spectacle article .gutter-small.prop-row>.column,#spectacle article .gutter-small.prop-row>.columns,#spectacle article .gutter-small.prop-row>.doc-copy,#spectacle article .gutter-small.prop-row>.doc-examples,#spectacle article .gutter-small.prop-row>.prop-name,#spectacle article .gutter-small.prop-row>.prop-value,#spectacle article .prop-row .gutter-small.doc-row>.prop-name,#spectacle article .prop-row .gutter-small.doc-row>.prop-value,#spectacle article .prop-row .row.gutter-small>.prop-name,#spectacle article .prop-row .row.gutter-small>.prop-value,#spectacle article .row.gutter-small>.doc-copy,#spectacle article .row.gutter-small>.doc-examples{padding-right:.6578947368rem;padding-left:.6578947368rem}#spectacle .row.gutter-medium>.column,#spectacle .row.gutter-medium>.columns,#spectacle article .gutter-medium.doc-row>.column,#spectacle article .gutter-medium.doc-row>.columns,#spectacle article .gutter-medium.doc-row>.doc-copy,#spectacle article .gutter-medium.doc-row>.doc-examples,#spectacle article .gutter-medium.prop-row>.column,#spectacle article .gutter-medium.prop-row>.columns,#spectacle article .gutter-medium.prop-row>.doc-copy,#spectacle article .gutter-medium.prop-row>.doc-examples,#spectacle article .gutter-medium.prop-row>.prop-name,#spectacle article .gutter-medium.prop-row>.prop-value,#spectacle article .prop-row .gutter-medium.doc-row>.prop-name,#spectacle article .prop-row .gutter-medium.doc-row>.prop-value,#spectacle article .prop-row .row.gutter-medium>.prop-name,#spectacle article .prop-row .row.gutter-medium>.prop-value,#spectacle article .row.gutter-medium>.doc-copy,#spectacle article .row.gutter-medium>.doc-examples{padding-right:.9868421053rem;padding-left:.9868421053rem}#spectacle .column,#spectacle .columns,#spectacle article .doc-copy,#spectacle article .doc-examples,#spectacle article .prop-row .prop-name,#spectacle article .prop-row .prop-value{width:100%;float:left;padding-right:.6578947368rem;padding-left:.6578947368rem}@media print,screen and (min-width:40em){#spectacle .column,#spectacle .columns,#spectacle article .doc-copy,#spectacle article .doc-examples,#spectacle article .prop-row .prop-name,#spectacle article .prop-row .prop-value{padding-right:.9868421053rem;padding-left:.9868421053rem}}#spectacle .column:last-child:not(:first-child),#spectacle .columns:last-child:not(:first-child),#spectacle article .doc-copy:last-child:not(:first-child),#spectacle article .doc-examples:last-child:not(:first-child),#spectacle article .prop-row .prop-name:last-child:not(:first-child),#spectacle article .prop-row .prop-value:last-child:not(:first-child){float:right}#spectacle .column.end:last-child:last-child,#spectacle .end.columns:last-child:last-child,#spectacle article .end.doc-copy:last-child:last-child,#spectacle article .end.doc-examples:last-child:last-child,#spectacle article .prop-row .end.prop-name:last-child:last-child,#spectacle article .prop-row .end.prop-value:last-child:last-child{float:left}#spectacle .column.row.row,#spectacle .row.row.columns,#spectacle article .column.doc-row,#spectacle article .column.prop-row,#spectacle article .columns.doc-row,#spectacle article .columns.prop-row,#spectacle article .doc-row.doc-copy,#spectacle article .doc-row.doc-examples,#spectacle article .prop-row .prop-name.doc-row,#spectacle article .prop-row .prop-row.prop-name,#spectacle article .prop-row .prop-row.prop-value,#spectacle article .prop-row .prop-value.doc-row,#spectacle article .prop-row .row.row.prop-name,#spectacle article .prop-row .row.row.prop-value,#spectacle article .prop-row.doc-copy,#spectacle article .prop-row.doc-examples,#spectacle article .row.row.doc-copy,#spectacle article .row.row.doc-examples{float:none}#spectacle .row .column.row.row,#spectacle .row .row.row.columns,#spectacle .row article .column.doc-row,#spectacle .row article .column.prop-row,#spectacle .row article .columns.doc-row,#spectacle .row article .columns.prop-row,#spectacle .row article .doc-row.doc-copy,#spectacle .row article .doc-row.doc-examples,#spectacle .row article .prop-row .prop-name.doc-row,#spectacle .row article .prop-row .prop-row.prop-name,#spectacle .row article .prop-row .prop-row.prop-value,#spectacle .row article .prop-row .prop-value.doc-row,#spectacle .row article .prop-row.doc-copy,#spectacle .row article .prop-row.doc-examples,#spectacle .row article .row.row.doc-copy,#spectacle .row article .row.row.doc-examples,#spectacle article .doc-row .column.doc-row,#spectacle article .doc-row .column.prop-row,#spectacle article .doc-row .column.row.row,#spectacle article .doc-row .columns.doc-row,#spectacle article .doc-row .columns.prop-row,#spectacle article .doc-row .doc-row.doc-copy,#spectacle article .doc-row .doc-row.doc-examples,#spectacle article .doc-row .prop-row.doc-copy,#spectacle article .doc-row .prop-row.doc-examples,#spectacle article .doc-row .row.row.columns,#spectacle article .doc-row .row.row.doc-copy,#spectacle article .doc-row .row.row.doc-examples,#spectacle article .prop-row .column.doc-row,#spectacle article .prop-row .column.prop-row,#spectacle article .prop-row .column.row.row,#spectacle article .prop-row .columns.doc-row,#spectacle article .prop-row .columns.prop-row,#spectacle article .prop-row .doc-row.doc-copy,#spectacle article .prop-row .doc-row.doc-examples,#spectacle article .prop-row .prop-name.doc-row,#spectacle article .prop-row .prop-row.doc-copy,#spectacle article .prop-row .prop-row.doc-examples,#spectacle article .prop-row .prop-row.prop-name,#spectacle article .prop-row .prop-row.prop-value,#spectacle article .prop-row .prop-value.doc-row,#spectacle article .prop-row .row .prop-name.doc-row,#spectacle article .prop-row .row .prop-row.prop-name,#spectacle article .prop-row .row .prop-row.prop-value,#spectacle article .prop-row .row .prop-value.doc-row,#spectacle article .prop-row .row.row.columns,#spectacle article .prop-row .row.row.doc-copy,#spectacle article .prop-row .row.row.doc-examples,#spectacle article .prop-row .row.row.prop-name,#spectacle article .prop-row .row.row.prop-value,#spectacle article .row .column.doc-row,#spectacle article .row .column.prop-row,#spectacle article .row .columns.doc-row,#spectacle article .row .columns.prop-row,#spectacle article .row .doc-row.doc-copy,#spectacle article .row .doc-row.doc-examples,#spectacle article .row .prop-row.doc-copy,#spectacle article .row .prop-row.doc-examples,#spectacle article .row .row.row.doc-copy,#spectacle article .row .row.row.doc-examples{margin-right:0;margin-left:0;padding-right:0;padding-left:0}#spectacle .small-1{width:8.3333333333%}#spectacle .small-push-1{position:relative;left:8.3333333333%}#spectacle .small-pull-1{position:relative;left:-8.3333333333%}#spectacle .small-offset-0{margin-left:0}#spectacle .small-2{width:16.6666666667%}#spectacle .small-push-2{position:relative;left:16.6666666667%}#spectacle .small-pull-2{position:relative;left:-16.6666666667%}#spectacle .small-offset-1{margin-left:8.3333333333%}#spectacle .small-3{width:25%}#spectacle .small-push-3{position:relative;left:25%}#spectacle .small-pull-3{position:relative;left:-25%}#spectacle .small-offset-2{margin-left:16.6666666667%}#spectacle .small-4{width:33.3333333333%}#spectacle .small-push-4{position:relative;left:33.3333333333%}#spectacle .small-pull-4{position:relative;left:-33.3333333333%}#spectacle .small-offset-3{margin-left:25%}#spectacle .small-5,#spectacle article .prop-row .prop-name{width:41.6666666667%}#spectacle .small-push-5{position:relative;left:41.6666666667%}#spectacle .small-pull-5{position:relative;left:-41.6666666667%}#spectacle .small-offset-4{margin-left:33.3333333333%}#spectacle .small-6{width:50%}#spectacle .small-push-6{position:relative;left:50%}#spectacle .small-pull-6{position:relative;left:-50%}#spectacle .small-offset-5{margin-left:41.6666666667%}#spectacle .small-7,#spectacle article .prop-row .prop-value{width:58.3333333333%}#spectacle .small-push-7{position:relative;left:58.3333333333%}#spectacle .small-pull-7{position:relative;left:-58.3333333333%}#spectacle .small-offset-6{margin-left:50%}#spectacle .small-8{width:66.6666666667%}#spectacle .small-push-8{position:relative;left:66.6666666667%}#spectacle .small-pull-8{position:relative;left:-66.6666666667%}#spectacle .small-offset-7{margin-left:58.3333333333%}#spectacle .small-9{width:75%}#spectacle .small-push-9{position:relative;left:75%}#spectacle .small-pull-9{position:relative;left:-75%}#spectacle .small-offset-8{margin-left:66.6666666667%}#spectacle .small-10{width:83.3333333333%}#spectacle .small-push-10{position:relative;left:83.3333333333%}#spectacle .small-pull-10{position:relative;left:-83.3333333333%}#spectacle .small-offset-9{margin-left:75%}#spectacle .small-11{width:91.6666666667%}#spectacle .small-push-11{position:relative;left:91.6666666667%}#spectacle .small-pull-11{position:relative;left:-91.6666666667%}#spectacle .small-offset-10{margin-left:83.3333333333%}#spectacle .small-12{width:100%}#spectacle .small-offset-11{margin-left:91.6666666667%}#spectacle .small-up-1>.column,#spectacle .small-up-1>.columns,#spectacle article .prop-row .small-up-1>.prop-name,#spectacle article .prop-row .small-up-1>.prop-value,#spectacle article .small-up-1>.doc-copy,#spectacle article .small-up-1>.doc-examples{float:left;width:100%}#spectacle .small-up-1>.column:nth-of-type(1n),#spectacle .small-up-1>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-1>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-1>.prop-value:nth-of-type(1n),#spectacle article .small-up-1>.doc-copy:nth-of-type(1n),#spectacle article .small-up-1>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-1>.column:nth-of-type(1n+1),#spectacle .small-up-1>.columns:nth-of-type(1n+1),#spectacle article .prop-row .small-up-1>.prop-name:nth-of-type(1n+1),#spectacle article .prop-row .small-up-1>.prop-value:nth-of-type(1n+1),#spectacle article .small-up-1>.doc-copy:nth-of-type(1n+1),#spectacle article .small-up-1>.doc-examples:nth-of-type(1n+1){clear:both}#spectacle .small-up-1>.column:last-child,#spectacle .small-up-1>.columns:last-child,#spectacle article .prop-row .small-up-1>.prop-name:last-child,#spectacle article .prop-row .small-up-1>.prop-value:last-child,#spectacle article .small-up-1>.doc-copy:last-child,#spectacle article .small-up-1>.doc-examples:last-child{float:left}#spectacle .small-up-2>.column,#spectacle .small-up-2>.columns,#spectacle article .prop-row .small-up-2>.prop-name,#spectacle article .prop-row .small-up-2>.prop-value,#spectacle article .small-up-2>.doc-copy,#spectacle article .small-up-2>.doc-examples{float:left;width:50%}#spectacle .small-up-2>.column:nth-of-type(1n),#spectacle .small-up-2>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-2>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-2>.prop-value:nth-of-type(1n),#spectacle article .small-up-2>.doc-copy:nth-of-type(1n),#spectacle article .small-up-2>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-2>.column:nth-of-type(2n+1),#spectacle .small-up-2>.columns:nth-of-type(2n+1),#spectacle article .prop-row .small-up-2>.prop-name:nth-of-type(2n+1),#spectacle article .prop-row .small-up-2>.prop-value:nth-of-type(2n+1),#spectacle article .small-up-2>.doc-copy:nth-of-type(2n+1),#spectacle article .small-up-2>.doc-examples:nth-of-type(2n+1){clear:both}#spectacle .small-up-2>.column:last-child,#spectacle .small-up-2>.columns:last-child,#spectacle article .prop-row .small-up-2>.prop-name:last-child,#spectacle article .prop-row .small-up-2>.prop-value:last-child,#spectacle article .small-up-2>.doc-copy:last-child,#spectacle article .small-up-2>.doc-examples:last-child{float:left}#spectacle .small-up-3>.column,#spectacle .small-up-3>.columns,#spectacle article .prop-row .small-up-3>.prop-name,#spectacle article .prop-row .small-up-3>.prop-value,#spectacle article .small-up-3>.doc-copy,#spectacle article .small-up-3>.doc-examples{float:left;width:33.3333333333%}#spectacle .small-up-3>.column:nth-of-type(1n),#spectacle .small-up-3>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-3>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-3>.prop-value:nth-of-type(1n),#spectacle article .small-up-3>.doc-copy:nth-of-type(1n),#spectacle article .small-up-3>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-3>.column:nth-of-type(3n+1),#spectacle .small-up-3>.columns:nth-of-type(3n+1),#spectacle article .prop-row .small-up-3>.prop-name:nth-of-type(3n+1),#spectacle article .prop-row .small-up-3>.prop-value:nth-of-type(3n+1),#spectacle article .small-up-3>.doc-copy:nth-of-type(3n+1),#spectacle article .small-up-3>.doc-examples:nth-of-type(3n+1){clear:both}#spectacle .small-up-3>.column:last-child,#spectacle .small-up-3>.columns:last-child,#spectacle article .prop-row .small-up-3>.prop-name:last-child,#spectacle article .prop-row .small-up-3>.prop-value:last-child,#spectacle article .small-up-3>.doc-copy:last-child,#spectacle article .small-up-3>.doc-examples:last-child{float:left}#spectacle .small-up-4>.column,#spectacle .small-up-4>.columns,#spectacle article .prop-row .small-up-4>.prop-name,#spectacle article .prop-row .small-up-4>.prop-value,#spectacle article .small-up-4>.doc-copy,#spectacle article .small-up-4>.doc-examples{float:left;width:25%}#spectacle .small-up-4>.column:nth-of-type(1n),#spectacle .small-up-4>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-4>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-4>.prop-value:nth-of-type(1n),#spectacle article .small-up-4>.doc-copy:nth-of-type(1n),#spectacle article .small-up-4>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-4>.column:nth-of-type(4n+1),#spectacle .small-up-4>.columns:nth-of-type(4n+1),#spectacle article .prop-row .small-up-4>.prop-name:nth-of-type(4n+1),#spectacle article .prop-row .small-up-4>.prop-value:nth-of-type(4n+1),#spectacle article .small-up-4>.doc-copy:nth-of-type(4n+1),#spectacle article .small-up-4>.doc-examples:nth-of-type(4n+1){clear:both}#spectacle .small-up-4>.column:last-child,#spectacle .small-up-4>.columns:last-child,#spectacle article .prop-row .small-up-4>.prop-name:last-child,#spectacle article .prop-row .small-up-4>.prop-value:last-child,#spectacle article .small-up-4>.doc-copy:last-child,#spectacle article .small-up-4>.doc-examples:last-child{float:left}#spectacle .small-up-5>.column,#spectacle .small-up-5>.columns,#spectacle article .prop-row .small-up-5>.prop-name,#spectacle article .prop-row .small-up-5>.prop-value,#spectacle article .small-up-5>.doc-copy,#spectacle article .small-up-5>.doc-examples{float:left;width:20%}#spectacle .small-up-5>.column:nth-of-type(1n),#spectacle .small-up-5>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-5>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-5>.prop-value:nth-of-type(1n),#spectacle article .small-up-5>.doc-copy:nth-of-type(1n),#spectacle article .small-up-5>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-5>.column:nth-of-type(5n+1),#spectacle .small-up-5>.columns:nth-of-type(5n+1),#spectacle article .prop-row .small-up-5>.prop-name:nth-of-type(5n+1),#spectacle article .prop-row .small-up-5>.prop-value:nth-of-type(5n+1),#spectacle article .small-up-5>.doc-copy:nth-of-type(5n+1),#spectacle article .small-up-5>.doc-examples:nth-of-type(5n+1){clear:both}#spectacle .small-up-5>.column:last-child,#spectacle .small-up-5>.columns:last-child,#spectacle article .prop-row .small-up-5>.prop-name:last-child,#spectacle article .prop-row .small-up-5>.prop-value:last-child,#spectacle article .small-up-5>.doc-copy:last-child,#spectacle article .small-up-5>.doc-examples:last-child{float:left}#spectacle .small-up-6>.column,#spectacle .small-up-6>.columns,#spectacle article .prop-row .small-up-6>.prop-name,#spectacle article .prop-row .small-up-6>.prop-value,#spectacle article .small-up-6>.doc-copy,#spectacle article .small-up-6>.doc-examples{float:left;width:16.6666666667%}#spectacle .small-up-6>.column:nth-of-type(1n),#spectacle .small-up-6>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-6>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-6>.prop-value:nth-of-type(1n),#spectacle article .small-up-6>.doc-copy:nth-of-type(1n),#spectacle article .small-up-6>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-6>.column:nth-of-type(6n+1),#spectacle .small-up-6>.columns:nth-of-type(6n+1),#spectacle article .prop-row .small-up-6>.prop-name:nth-of-type(6n+1),#spectacle article .prop-row .small-up-6>.prop-value:nth-of-type(6n+1),#spectacle article .small-up-6>.doc-copy:nth-of-type(6n+1),#spectacle article .small-up-6>.doc-examples:nth-of-type(6n+1){clear:both}#spectacle .small-up-6>.column:last-child,#spectacle .small-up-6>.columns:last-child,#spectacle article .prop-row .small-up-6>.prop-name:last-child,#spectacle article .prop-row .small-up-6>.prop-value:last-child,#spectacle article .small-up-6>.doc-copy:last-child,#spectacle article .small-up-6>.doc-examples:last-child{float:left}#spectacle .small-up-7>.column,#spectacle .small-up-7>.columns,#spectacle article .prop-row .small-up-7>.prop-name,#spectacle article .prop-row .small-up-7>.prop-value,#spectacle article .small-up-7>.doc-copy,#spectacle article .small-up-7>.doc-examples{float:left;width:14.2857142857%}#spectacle .small-up-7>.column:nth-of-type(1n),#spectacle .small-up-7>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-7>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-7>.prop-value:nth-of-type(1n),#spectacle article .small-up-7>.doc-copy:nth-of-type(1n),#spectacle article .small-up-7>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-7>.column:nth-of-type(7n+1),#spectacle .small-up-7>.columns:nth-of-type(7n+1),#spectacle article .prop-row .small-up-7>.prop-name:nth-of-type(7n+1),#spectacle article .prop-row .small-up-7>.prop-value:nth-of-type(7n+1),#spectacle article .small-up-7>.doc-copy:nth-of-type(7n+1),#spectacle article .small-up-7>.doc-examples:nth-of-type(7n+1){clear:both}#spectacle .small-up-7>.column:last-child,#spectacle .small-up-7>.columns:last-child,#spectacle article .prop-row .small-up-7>.prop-name:last-child,#spectacle article .prop-row .small-up-7>.prop-value:last-child,#spectacle article .small-up-7>.doc-copy:last-child,#spectacle article .small-up-7>.doc-examples:last-child{float:left}#spectacle .small-up-8>.column,#spectacle .small-up-8>.columns,#spectacle article .prop-row .small-up-8>.prop-name,#spectacle article .prop-row .small-up-8>.prop-value,#spectacle article .small-up-8>.doc-copy,#spectacle article .small-up-8>.doc-examples{float:left;width:12.5%}#spectacle .small-up-8>.column:nth-of-type(1n),#spectacle .small-up-8>.columns:nth-of-type(1n),#spectacle article .prop-row .small-up-8>.prop-name:nth-of-type(1n),#spectacle article .prop-row .small-up-8>.prop-value:nth-of-type(1n),#spectacle article .small-up-8>.doc-copy:nth-of-type(1n),#spectacle article .small-up-8>.doc-examples:nth-of-type(1n){clear:none}#spectacle .small-up-8>.column:nth-of-type(8n+1),#spectacle .small-up-8>.columns:nth-of-type(8n+1),#spectacle article .prop-row .small-up-8>.prop-name:nth-of-type(8n+1),#spectacle article .prop-row .small-up-8>.prop-value:nth-of-type(8n+1),#spectacle article .small-up-8>.doc-copy:nth-of-type(8n+1),#spectacle article .small-up-8>.doc-examples:nth-of-type(8n+1){clear:both}#spectacle .small-up-8>.column:last-child,#spectacle .small-up-8>.columns:last-child,#spectacle article .prop-row .small-up-8>.prop-name:last-child,#spectacle article .prop-row .small-up-8>.prop-value:last-child,#spectacle article .small-up-8>.doc-copy:last-child,#spectacle article .small-up-8>.doc-examples:last-child{float:left}#spectacle .small-collapse>.column,#spectacle .small-collapse>.columns,#spectacle article .prop-row .small-collapse>.prop-name,#spectacle article .prop-row .small-collapse>.prop-value,#spectacle article .small-collapse>.doc-copy,#spectacle article .small-collapse>.doc-examples{padding-right:0;padding-left:0}#spectacle .small-collapse .row,#spectacle .small-collapse article .doc-row,#spectacle .small-collapse article .prop-row,#spectacle article .small-collapse .doc-row,#spectacle article .small-collapse .prop-row{margin-right:0;margin-left:0}#spectacle .expanded.row .small-collapse.row,#spectacle .expanded.row article .small-collapse.doc-row,#spectacle .expanded.row article .small-collapse.prop-row,#spectacle article .expanded.doc-row .small-collapse.doc-row,#spectacle article .expanded.doc-row .small-collapse.prop-row,#spectacle article .expanded.doc-row .small-collapse.row,#spectacle article .expanded.prop-row .small-collapse.doc-row,#spectacle article .expanded.prop-row .small-collapse.prop-row,#spectacle article .expanded.prop-row .small-collapse.row,#spectacle article .expanded.row .small-collapse.doc-row,#spectacle article .expanded.row .small-collapse.prop-row{margin-right:0;margin-left:0}#spectacle .small-uncollapse>.column,#spectacle .small-uncollapse>.columns,#spectacle article .prop-row .small-uncollapse>.prop-name,#spectacle article .prop-row .small-uncollapse>.prop-value,#spectacle article .small-uncollapse>.doc-copy,#spectacle article .small-uncollapse>.doc-examples{padding-right:.6578947368rem;padding-left:.6578947368rem}#spectacle .small-centered{margin-right:auto;margin-left:auto}#spectacle .small-centered,#spectacle .small-centered:last-child:not(:first-child){float:none;clear:both}#spectacle .small-pull-0,#spectacle .small-push-0,#spectacle .small-uncentered{position:static;float:left;margin-right:0;margin-left:0}@media print,screen and (min-width:40em){#spectacle .medium-1{width:8.3333333333%}#spectacle .medium-push-1{position:relative;left:8.3333333333%}#spectacle .medium-pull-1{position:relative;left:-8.3333333333%}#spectacle .medium-offset-0{margin-left:0}#spectacle .medium-2{width:16.6666666667%}#spectacle .medium-push-2{position:relative;left:16.6666666667%}#spectacle .medium-pull-2{position:relative;left:-16.6666666667%}#spectacle .medium-offset-1{margin-left:8.3333333333%}#spectacle .medium-3{width:25%}#spectacle .medium-push-3{position:relative;left:25%}#spectacle .medium-pull-3{position:relative;left:-25%}#spectacle .medium-offset-2{margin-left:16.6666666667%}#spectacle .medium-4{width:33.3333333333%}#spectacle .medium-push-4{position:relative;left:33.3333333333%}#spectacle .medium-pull-4{position:relative;left:-33.3333333333%}#spectacle .medium-offset-3{margin-left:25%}#spectacle .medium-5{width:41.6666666667%}#spectacle .medium-push-5{position:relative;left:41.6666666667%}#spectacle .medium-pull-5{position:relative;left:-41.6666666667%}#spectacle .medium-offset-4{margin-left:33.3333333333%}#spectacle .medium-6{width:50%}#spectacle .medium-push-6{position:relative;left:50%}#spectacle .medium-pull-6{position:relative;left:-50%}#spectacle .medium-offset-5{margin-left:41.6666666667%}#spectacle .medium-7{width:58.3333333333%}#spectacle .medium-push-7{position:relative;left:58.3333333333%}#spectacle .medium-pull-7{position:relative;left:-58.3333333333%}#spectacle .medium-offset-6{margin-left:50%}#spectacle .medium-8{width:66.6666666667%}#spectacle .medium-push-8{position:relative;left:66.6666666667%}#spectacle .medium-pull-8{position:relative;left:-66.6666666667%}#spectacle .medium-offset-7{margin-left:58.3333333333%}#spectacle .medium-9{width:75%}#spectacle .medium-push-9{position:relative;left:75%}#spectacle .medium-pull-9{position:relative;left:-75%}#spectacle .medium-offset-8{margin-left:66.6666666667%}#spectacle .medium-10{width:83.3333333333%}#spectacle .medium-push-10{position:relative;left:83.3333333333%}#spectacle .medium-pull-10{position:relative;left:-83.3333333333%}#spectacle .medium-offset-9{margin-left:75%}#spectacle .medium-11{width:91.6666666667%}#spectacle .medium-push-11{position:relative;left:91.6666666667%}#spectacle .medium-pull-11{position:relative;left:-91.6666666667%}#spectacle .medium-offset-10{margin-left:83.3333333333%}#spectacle .medium-12{width:100%}#spectacle .medium-offset-11{margin-left:91.6666666667%}#spectacle .medium-up-1>.column,#spectacle .medium-up-1>.columns,#spectacle article .medium-up-1>.doc-copy,#spectacle article .medium-up-1>.doc-examples,#spectacle article .prop-row .medium-up-1>.prop-name,#spectacle article .prop-row .medium-up-1>.prop-value{float:left;width:100%}#spectacle .medium-up-1>.column:nth-of-type(1n),#spectacle .medium-up-1>.columns:nth-of-type(1n),#spectacle article .medium-up-1>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-1>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-1>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-1>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-1>.column:nth-of-type(1n+1),#spectacle .medium-up-1>.columns:nth-of-type(1n+1),#spectacle article .medium-up-1>.doc-copy:nth-of-type(1n+1),#spectacle article .medium-up-1>.doc-examples:nth-of-type(1n+1),#spectacle article .prop-row .medium-up-1>.prop-name:nth-of-type(1n+1),#spectacle article .prop-row .medium-up-1>.prop-value:nth-of-type(1n+1){clear:both}#spectacle .medium-up-1>.column:last-child,#spectacle .medium-up-1>.columns:last-child,#spectacle article .medium-up-1>.doc-copy:last-child,#spectacle article .medium-up-1>.doc-examples:last-child,#spectacle article .prop-row .medium-up-1>.prop-name:last-child,#spectacle article .prop-row .medium-up-1>.prop-value:last-child{float:left}#spectacle .medium-up-2>.column,#spectacle .medium-up-2>.columns,#spectacle article .medium-up-2>.doc-copy,#spectacle article .medium-up-2>.doc-examples,#spectacle article .prop-row .medium-up-2>.prop-name,#spectacle article .prop-row .medium-up-2>.prop-value{float:left;width:50%}#spectacle .medium-up-2>.column:nth-of-type(1n),#spectacle .medium-up-2>.columns:nth-of-type(1n),#spectacle article .medium-up-2>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-2>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-2>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-2>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-2>.column:nth-of-type(2n+1),#spectacle .medium-up-2>.columns:nth-of-type(2n+1),#spectacle article .medium-up-2>.doc-copy:nth-of-type(2n+1),#spectacle article .medium-up-2>.doc-examples:nth-of-type(2n+1),#spectacle article .prop-row .medium-up-2>.prop-name:nth-of-type(2n+1),#spectacle article .prop-row .medium-up-2>.prop-value:nth-of-type(2n+1){clear:both}#spectacle .medium-up-2>.column:last-child,#spectacle .medium-up-2>.columns:last-child,#spectacle article .medium-up-2>.doc-copy:last-child,#spectacle article .medium-up-2>.doc-examples:last-child,#spectacle article .prop-row .medium-up-2>.prop-name:last-child,#spectacle article .prop-row .medium-up-2>.prop-value:last-child{float:left}#spectacle .medium-up-3>.column,#spectacle .medium-up-3>.columns,#spectacle article .medium-up-3>.doc-copy,#spectacle article .medium-up-3>.doc-examples,#spectacle article .prop-row .medium-up-3>.prop-name,#spectacle article .prop-row .medium-up-3>.prop-value{float:left;width:33.3333333333%}#spectacle .medium-up-3>.column:nth-of-type(1n),#spectacle .medium-up-3>.columns:nth-of-type(1n),#spectacle article .medium-up-3>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-3>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-3>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-3>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-3>.column:nth-of-type(3n+1),#spectacle .medium-up-3>.columns:nth-of-type(3n+1),#spectacle article .medium-up-3>.doc-copy:nth-of-type(3n+1),#spectacle article .medium-up-3>.doc-examples:nth-of-type(3n+1),#spectacle article .prop-row .medium-up-3>.prop-name:nth-of-type(3n+1),#spectacle article .prop-row .medium-up-3>.prop-value:nth-of-type(3n+1){clear:both}#spectacle .medium-up-3>.column:last-child,#spectacle .medium-up-3>.columns:last-child,#spectacle article .medium-up-3>.doc-copy:last-child,#spectacle article .medium-up-3>.doc-examples:last-child,#spectacle article .prop-row .medium-up-3>.prop-name:last-child,#spectacle article .prop-row .medium-up-3>.prop-value:last-child{float:left}#spectacle .medium-up-4>.column,#spectacle .medium-up-4>.columns,#spectacle article .medium-up-4>.doc-copy,#spectacle article .medium-up-4>.doc-examples,#spectacle article .prop-row .medium-up-4>.prop-name,#spectacle article .prop-row .medium-up-4>.prop-value{float:left;width:25%}#spectacle .medium-up-4>.column:nth-of-type(1n),#spectacle .medium-up-4>.columns:nth-of-type(1n),#spectacle article .medium-up-4>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-4>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-4>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-4>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-4>.column:nth-of-type(4n+1),#spectacle .medium-up-4>.columns:nth-of-type(4n+1),#spectacle article .medium-up-4>.doc-copy:nth-of-type(4n+1),#spectacle article .medium-up-4>.doc-examples:nth-of-type(4n+1),#spectacle article .prop-row .medium-up-4>.prop-name:nth-of-type(4n+1),#spectacle article .prop-row .medium-up-4>.prop-value:nth-of-type(4n+1){clear:both}#spectacle .medium-up-4>.column:last-child,#spectacle .medium-up-4>.columns:last-child,#spectacle article .medium-up-4>.doc-copy:last-child,#spectacle article .medium-up-4>.doc-examples:last-child,#spectacle article .prop-row .medium-up-4>.prop-name:last-child,#spectacle article .prop-row .medium-up-4>.prop-value:last-child{float:left}#spectacle .medium-up-5>.column,#spectacle .medium-up-5>.columns,#spectacle article .medium-up-5>.doc-copy,#spectacle article .medium-up-5>.doc-examples,#spectacle article .prop-row .medium-up-5>.prop-name,#spectacle article .prop-row .medium-up-5>.prop-value{float:left;width:20%}#spectacle .medium-up-5>.column:nth-of-type(1n),#spectacle .medium-up-5>.columns:nth-of-type(1n),#spectacle article .medium-up-5>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-5>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-5>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-5>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-5>.column:nth-of-type(5n+1),#spectacle .medium-up-5>.columns:nth-of-type(5n+1),#spectacle article .medium-up-5>.doc-copy:nth-of-type(5n+1),#spectacle article .medium-up-5>.doc-examples:nth-of-type(5n+1),#spectacle article .prop-row .medium-up-5>.prop-name:nth-of-type(5n+1),#spectacle article .prop-row .medium-up-5>.prop-value:nth-of-type(5n+1){clear:both}#spectacle .medium-up-5>.column:last-child,#spectacle .medium-up-5>.columns:last-child,#spectacle article .medium-up-5>.doc-copy:last-child,#spectacle article .medium-up-5>.doc-examples:last-child,#spectacle article .prop-row .medium-up-5>.prop-name:last-child,#spectacle article .prop-row .medium-up-5>.prop-value:last-child{float:left}#spectacle .medium-up-6>.column,#spectacle .medium-up-6>.columns,#spectacle article .medium-up-6>.doc-copy,#spectacle article .medium-up-6>.doc-examples,#spectacle article .prop-row .medium-up-6>.prop-name,#spectacle article .prop-row .medium-up-6>.prop-value{float:left;width:16.6666666667%}#spectacle .medium-up-6>.column:nth-of-type(1n),#spectacle .medium-up-6>.columns:nth-of-type(1n),#spectacle article .medium-up-6>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-6>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-6>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-6>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-6>.column:nth-of-type(6n+1),#spectacle .medium-up-6>.columns:nth-of-type(6n+1),#spectacle article .medium-up-6>.doc-copy:nth-of-type(6n+1),#spectacle article .medium-up-6>.doc-examples:nth-of-type(6n+1),#spectacle article .prop-row .medium-up-6>.prop-name:nth-of-type(6n+1),#spectacle article .prop-row .medium-up-6>.prop-value:nth-of-type(6n+1){clear:both}#spectacle .medium-up-6>.column:last-child,#spectacle .medium-up-6>.columns:last-child,#spectacle article .medium-up-6>.doc-copy:last-child,#spectacle article .medium-up-6>.doc-examples:last-child,#spectacle article .prop-row .medium-up-6>.prop-name:last-child,#spectacle article .prop-row .medium-up-6>.prop-value:last-child{float:left}#spectacle .medium-up-7>.column,#spectacle .medium-up-7>.columns,#spectacle article .medium-up-7>.doc-copy,#spectacle article .medium-up-7>.doc-examples,#spectacle article .prop-row .medium-up-7>.prop-name,#spectacle article .prop-row .medium-up-7>.prop-value{float:left;width:14.2857142857%}#spectacle .medium-up-7>.column:nth-of-type(1n),#spectacle .medium-up-7>.columns:nth-of-type(1n),#spectacle article .medium-up-7>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-7>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-7>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-7>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-7>.column:nth-of-type(7n+1),#spectacle .medium-up-7>.columns:nth-of-type(7n+1),#spectacle article .medium-up-7>.doc-copy:nth-of-type(7n+1),#spectacle article .medium-up-7>.doc-examples:nth-of-type(7n+1),#spectacle article .prop-row .medium-up-7>.prop-name:nth-of-type(7n+1),#spectacle article .prop-row .medium-up-7>.prop-value:nth-of-type(7n+1){clear:both}#spectacle .medium-up-7>.column:last-child,#spectacle .medium-up-7>.columns:last-child,#spectacle article .medium-up-7>.doc-copy:last-child,#spectacle article .medium-up-7>.doc-examples:last-child,#spectacle article .prop-row .medium-up-7>.prop-name:last-child,#spectacle article .prop-row .medium-up-7>.prop-value:last-child{float:left}#spectacle .medium-up-8>.column,#spectacle .medium-up-8>.columns,#spectacle article .medium-up-8>.doc-copy,#spectacle article .medium-up-8>.doc-examples,#spectacle article .prop-row .medium-up-8>.prop-name,#spectacle article .prop-row .medium-up-8>.prop-value{float:left;width:12.5%}#spectacle .medium-up-8>.column:nth-of-type(1n),#spectacle .medium-up-8>.columns:nth-of-type(1n),#spectacle article .medium-up-8>.doc-copy:nth-of-type(1n),#spectacle article .medium-up-8>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .medium-up-8>.prop-name:nth-of-type(1n),#spectacle article .prop-row .medium-up-8>.prop-value:nth-of-type(1n){clear:none}#spectacle .medium-up-8>.column:nth-of-type(8n+1),#spectacle .medium-up-8>.columns:nth-of-type(8n+1),#spectacle article .medium-up-8>.doc-copy:nth-of-type(8n+1),#spectacle article .medium-up-8>.doc-examples:nth-of-type(8n+1),#spectacle article .prop-row .medium-up-8>.prop-name:nth-of-type(8n+1),#spectacle article .prop-row .medium-up-8>.prop-value:nth-of-type(8n+1){clear:both}#spectacle .medium-up-8>.column:last-child,#spectacle .medium-up-8>.columns:last-child,#spectacle article .medium-up-8>.doc-copy:last-child,#spectacle article .medium-up-8>.doc-examples:last-child,#spectacle article .prop-row .medium-up-8>.prop-name:last-child,#spectacle article .prop-row .medium-up-8>.prop-value:last-child{float:left}#spectacle .medium-collapse>.column,#spectacle .medium-collapse>.columns,#spectacle article .medium-collapse>.doc-copy,#spectacle article .medium-collapse>.doc-examples,#spectacle article .prop-row .medium-collapse>.prop-name,#spectacle article .prop-row .medium-collapse>.prop-value{padding-right:0;padding-left:0}#spectacle .medium-collapse .row,#spectacle .medium-collapse article .doc-row,#spectacle .medium-collapse article .prop-row,#spectacle article .medium-collapse .doc-row,#spectacle article .medium-collapse .prop-row{margin-right:0;margin-left:0}#spectacle .expanded.row .medium-collapse.row,#spectacle .expanded.row article .medium-collapse.doc-row,#spectacle .expanded.row article .medium-collapse.prop-row,#spectacle article .expanded.doc-row .medium-collapse.doc-row,#spectacle article .expanded.doc-row .medium-collapse.prop-row,#spectacle article .expanded.doc-row .medium-collapse.row,#spectacle article .expanded.prop-row .medium-collapse.doc-row,#spectacle article .expanded.prop-row .medium-collapse.prop-row,#spectacle article .expanded.prop-row .medium-collapse.row,#spectacle article .expanded.row .medium-collapse.doc-row,#spectacle article .expanded.row .medium-collapse.prop-row{margin-right:0;margin-left:0}#spectacle .medium-uncollapse>.column,#spectacle .medium-uncollapse>.columns,#spectacle article .medium-uncollapse>.doc-copy,#spectacle article .medium-uncollapse>.doc-examples,#spectacle article .prop-row .medium-uncollapse>.prop-name,#spectacle article .prop-row .medium-uncollapse>.prop-value{padding-right:.9868421053rem;padding-left:.9868421053rem}#spectacle .medium-centered{margin-right:auto;margin-left:auto}#spectacle .medium-centered,#spectacle .medium-centered:last-child:not(:first-child){float:none;clear:both}#spectacle .medium-pull-0,#spectacle .medium-push-0,#spectacle .medium-uncentered{position:static;float:left;margin-right:0;margin-left:0}}@media print,screen and (min-width:64em){#spectacle .large-1{width:8.3333333333%}#spectacle .large-push-1{position:relative;left:8.3333333333%}#spectacle .large-pull-1{position:relative;left:-8.3333333333%}#spectacle .large-offset-0{margin-left:0}#spectacle .large-2{width:16.6666666667%}#spectacle .large-push-2{position:relative;left:16.6666666667%}#spectacle .large-pull-2{position:relative;left:-16.6666666667%}#spectacle .large-offset-1{margin-left:8.3333333333%}#spectacle .large-3{width:25%}#spectacle .large-push-3{position:relative;left:25%}#spectacle .large-pull-3{position:relative;left:-25%}#spectacle .large-offset-2{margin-left:16.6666666667%}#spectacle .large-4{width:33.3333333333%}#spectacle .large-push-4{position:relative;left:33.3333333333%}#spectacle .large-pull-4{position:relative;left:-33.3333333333%}#spectacle .large-offset-3{margin-left:25%}#spectacle .large-5{width:41.6666666667%}#spectacle .large-push-5{position:relative;left:41.6666666667%}#spectacle .large-pull-5{position:relative;left:-41.6666666667%}#spectacle .large-offset-4{margin-left:33.3333333333%}#spectacle .doc-content,#spectacle .large-6,#spectacle article .doc-copy,#spectacle article .doc-examples,#spectacle article .panel>h2,#spectacle article .panel>h3,#spectacle article h1.doc-title,#spectacle article>h1,#spectacle article>h2{width:50%}#spectacle .large-push-6{position:relative;left:50%}#spectacle .large-pull-6{position:relative;left:-50%}#spectacle .large-offset-5{margin-left:41.6666666667%}#spectacle .large-7{width:58.3333333333%}#spectacle .large-push-7{position:relative;left:58.3333333333%}#spectacle .large-pull-7{position:relative;left:-58.3333333333%}#spectacle .large-offset-6{margin-left:50%}#spectacle .large-8{width:66.6666666667%}#spectacle .large-push-8{position:relative;left:66.6666666667%}#spectacle .large-pull-8{position:relative;left:-66.6666666667%}#spectacle .large-offset-7{margin-left:58.3333333333%}#spectacle .large-9{width:75%}#spectacle .large-push-9{position:relative;left:75%}#spectacle .large-pull-9{position:relative;left:-75%}#spectacle .large-offset-8{margin-left:66.6666666667%}#spectacle .large-10{width:83.3333333333%}#spectacle .large-push-10{position:relative;left:83.3333333333%}#spectacle .large-pull-10{position:relative;left:-83.3333333333%}#spectacle .large-offset-9{margin-left:75%}#spectacle .large-11{width:91.6666666667%}#spectacle .large-push-11{position:relative;left:91.6666666667%}#spectacle .large-pull-11{position:relative;left:-91.6666666667%}#spectacle .large-offset-10{margin-left:83.3333333333%}#spectacle .large-12{width:100%}#spectacle .large-offset-11{margin-left:91.6666666667%}#spectacle .large-up-1>.column,#spectacle .large-up-1>.columns,#spectacle article .large-up-1>.doc-copy,#spectacle article .large-up-1>.doc-examples,#spectacle article .prop-row .large-up-1>.prop-name,#spectacle article .prop-row .large-up-1>.prop-value{float:left;width:100%}#spectacle .large-up-1>.column:nth-of-type(1n),#spectacle .large-up-1>.columns:nth-of-type(1n),#spectacle article .large-up-1>.doc-copy:nth-of-type(1n),#spectacle article .large-up-1>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-1>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-1>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-1>.column:nth-of-type(1n+1),#spectacle .large-up-1>.columns:nth-of-type(1n+1),#spectacle article .large-up-1>.doc-copy:nth-of-type(1n+1),#spectacle article .large-up-1>.doc-examples:nth-of-type(1n+1),#spectacle article .prop-row .large-up-1>.prop-name:nth-of-type(1n+1),#spectacle article .prop-row .large-up-1>.prop-value:nth-of-type(1n+1){clear:both}#spectacle .large-up-1>.column:last-child,#spectacle .large-up-1>.columns:last-child,#spectacle article .large-up-1>.doc-copy:last-child,#spectacle article .large-up-1>.doc-examples:last-child,#spectacle article .prop-row .large-up-1>.prop-name:last-child,#spectacle article .prop-row .large-up-1>.prop-value:last-child{float:left}#spectacle .large-up-2>.column,#spectacle .large-up-2>.columns,#spectacle article .large-up-2>.doc-copy,#spectacle article .large-up-2>.doc-examples,#spectacle article .prop-row .large-up-2>.prop-name,#spectacle article .prop-row .large-up-2>.prop-value{float:left;width:50%}#spectacle .large-up-2>.column:nth-of-type(1n),#spectacle .large-up-2>.columns:nth-of-type(1n),#spectacle article .large-up-2>.doc-copy:nth-of-type(1n),#spectacle article .large-up-2>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-2>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-2>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-2>.column:nth-of-type(2n+1),#spectacle .large-up-2>.columns:nth-of-type(2n+1),#spectacle article .large-up-2>.doc-copy:nth-of-type(2n+1),#spectacle article .large-up-2>.doc-examples:nth-of-type(2n+1),#spectacle article .prop-row .large-up-2>.prop-name:nth-of-type(2n+1),#spectacle article .prop-row .large-up-2>.prop-value:nth-of-type(2n+1){clear:both}#spectacle .large-up-2>.column:last-child,#spectacle .large-up-2>.columns:last-child,#spectacle article .large-up-2>.doc-copy:last-child,#spectacle article .large-up-2>.doc-examples:last-child,#spectacle article .prop-row .large-up-2>.prop-name:last-child,#spectacle article .prop-row .large-up-2>.prop-value:last-child{float:left}#spectacle .large-up-3>.column,#spectacle .large-up-3>.columns,#spectacle article .large-up-3>.doc-copy,#spectacle article .large-up-3>.doc-examples,#spectacle article .prop-row .large-up-3>.prop-name,#spectacle article .prop-row .large-up-3>.prop-value{float:left;width:33.3333333333%}#spectacle .large-up-3>.column:nth-of-type(1n),#spectacle .large-up-3>.columns:nth-of-type(1n),#spectacle article .large-up-3>.doc-copy:nth-of-type(1n),#spectacle article .large-up-3>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-3>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-3>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-3>.column:nth-of-type(3n+1),#spectacle .large-up-3>.columns:nth-of-type(3n+1),#spectacle article .large-up-3>.doc-copy:nth-of-type(3n+1),#spectacle article .large-up-3>.doc-examples:nth-of-type(3n+1),#spectacle article .prop-row .large-up-3>.prop-name:nth-of-type(3n+1),#spectacle article .prop-row .large-up-3>.prop-value:nth-of-type(3n+1){clear:both}#spectacle .large-up-3>.column:last-child,#spectacle .large-up-3>.columns:last-child,#spectacle article .large-up-3>.doc-copy:last-child,#spectacle article .large-up-3>.doc-examples:last-child,#spectacle article .prop-row .large-up-3>.prop-name:last-child,#spectacle article .prop-row .large-up-3>.prop-value:last-child{float:left}#spectacle .large-up-4>.column,#spectacle .large-up-4>.columns,#spectacle article .large-up-4>.doc-copy,#spectacle article .large-up-4>.doc-examples,#spectacle article .prop-row .large-up-4>.prop-name,#spectacle article .prop-row .large-up-4>.prop-value{float:left;width:25%}#spectacle .large-up-4>.column:nth-of-type(1n),#spectacle .large-up-4>.columns:nth-of-type(1n),#spectacle article .large-up-4>.doc-copy:nth-of-type(1n),#spectacle article .large-up-4>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-4>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-4>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-4>.column:nth-of-type(4n+1),#spectacle .large-up-4>.columns:nth-of-type(4n+1),#spectacle article .large-up-4>.doc-copy:nth-of-type(4n+1),#spectacle article .large-up-4>.doc-examples:nth-of-type(4n+1),#spectacle article .prop-row .large-up-4>.prop-name:nth-of-type(4n+1),#spectacle article .prop-row .large-up-4>.prop-value:nth-of-type(4n+1){clear:both}#spectacle .large-up-4>.column:last-child,#spectacle .large-up-4>.columns:last-child,#spectacle article .large-up-4>.doc-copy:last-child,#spectacle article .large-up-4>.doc-examples:last-child,#spectacle article .prop-row .large-up-4>.prop-name:last-child,#spectacle article .prop-row .large-up-4>.prop-value:last-child{float:left}#spectacle .large-up-5>.column,#spectacle .large-up-5>.columns,#spectacle article .large-up-5>.doc-copy,#spectacle article .large-up-5>.doc-examples,#spectacle article .prop-row .large-up-5>.prop-name,#spectacle article .prop-row .large-up-5>.prop-value{float:left;width:20%}#spectacle .large-up-5>.column:nth-of-type(1n),#spectacle .large-up-5>.columns:nth-of-type(1n),#spectacle article .large-up-5>.doc-copy:nth-of-type(1n),#spectacle article .large-up-5>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-5>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-5>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-5>.column:nth-of-type(5n+1),#spectacle .large-up-5>.columns:nth-of-type(5n+1),#spectacle article .large-up-5>.doc-copy:nth-of-type(5n+1),#spectacle article .large-up-5>.doc-examples:nth-of-type(5n+1),#spectacle article .prop-row .large-up-5>.prop-name:nth-of-type(5n+1),#spectacle article .prop-row .large-up-5>.prop-value:nth-of-type(5n+1){clear:both}#spectacle .large-up-5>.column:last-child,#spectacle .large-up-5>.columns:last-child,#spectacle article .large-up-5>.doc-copy:last-child,#spectacle article .large-up-5>.doc-examples:last-child,#spectacle article .prop-row .large-up-5>.prop-name:last-child,#spectacle article .prop-row .large-up-5>.prop-value:last-child{float:left}#spectacle .large-up-6>.column,#spectacle .large-up-6>.columns,#spectacle article .large-up-6>.doc-copy,#spectacle article .large-up-6>.doc-examples,#spectacle article .prop-row .large-up-6>.prop-name,#spectacle article .prop-row .large-up-6>.prop-value{float:left;width:16.6666666667%}#spectacle .large-up-6>.column:nth-of-type(1n),#spectacle .large-up-6>.columns:nth-of-type(1n),#spectacle article .large-up-6>.doc-copy:nth-of-type(1n),#spectacle article .large-up-6>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-6>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-6>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-6>.column:nth-of-type(6n+1),#spectacle .large-up-6>.columns:nth-of-type(6n+1),#spectacle article .large-up-6>.doc-copy:nth-of-type(6n+1),#spectacle article .large-up-6>.doc-examples:nth-of-type(6n+1),#spectacle article .prop-row .large-up-6>.prop-name:nth-of-type(6n+1),#spectacle article .prop-row .large-up-6>.prop-value:nth-of-type(6n+1){clear:both}#spectacle .large-up-6>.column:last-child,#spectacle .large-up-6>.columns:last-child,#spectacle article .large-up-6>.doc-copy:last-child,#spectacle article .large-up-6>.doc-examples:last-child,#spectacle article .prop-row .large-up-6>.prop-name:last-child,#spectacle article .prop-row .large-up-6>.prop-value:last-child{float:left}#spectacle .large-up-7>.column,#spectacle .large-up-7>.columns,#spectacle article .large-up-7>.doc-copy,#spectacle article .large-up-7>.doc-examples,#spectacle article .prop-row .large-up-7>.prop-name,#spectacle article .prop-row .large-up-7>.prop-value{float:left;width:14.2857142857%}#spectacle .large-up-7>.column:nth-of-type(1n),#spectacle .large-up-7>.columns:nth-of-type(1n),#spectacle article .large-up-7>.doc-copy:nth-of-type(1n),#spectacle article .large-up-7>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-7>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-7>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-7>.column:nth-of-type(7n+1),#spectacle .large-up-7>.columns:nth-of-type(7n+1),#spectacle article .large-up-7>.doc-copy:nth-of-type(7n+1),#spectacle article .large-up-7>.doc-examples:nth-of-type(7n+1),#spectacle article .prop-row .large-up-7>.prop-name:nth-of-type(7n+1),#spectacle article .prop-row .large-up-7>.prop-value:nth-of-type(7n+1){clear:both}#spectacle .large-up-7>.column:last-child,#spectacle .large-up-7>.columns:last-child,#spectacle article .large-up-7>.doc-copy:last-child,#spectacle article .large-up-7>.doc-examples:last-child,#spectacle article .prop-row .large-up-7>.prop-name:last-child,#spectacle article .prop-row .large-up-7>.prop-value:last-child{float:left}#spectacle .large-up-8>.column,#spectacle .large-up-8>.columns,#spectacle article .large-up-8>.doc-copy,#spectacle article .large-up-8>.doc-examples,#spectacle article .prop-row .large-up-8>.prop-name,#spectacle article .prop-row .large-up-8>.prop-value{float:left;width:12.5%}#spectacle .large-up-8>.column:nth-of-type(1n),#spectacle .large-up-8>.columns:nth-of-type(1n),#spectacle article .large-up-8>.doc-copy:nth-of-type(1n),#spectacle article .large-up-8>.doc-examples:nth-of-type(1n),#spectacle article .prop-row .large-up-8>.prop-name:nth-of-type(1n),#spectacle article .prop-row .large-up-8>.prop-value:nth-of-type(1n){clear:none}#spectacle .large-up-8>.column:nth-of-type(8n+1),#spectacle .large-up-8>.columns:nth-of-type(8n+1),#spectacle article .large-up-8>.doc-copy:nth-of-type(8n+1),#spectacle article .large-up-8>.doc-examples:nth-of-type(8n+1),#spectacle article .prop-row .large-up-8>.prop-name:nth-of-type(8n+1),#spectacle article .prop-row .large-up-8>.prop-value:nth-of-type(8n+1){clear:both}#spectacle .large-up-8>.column:last-child,#spectacle .large-up-8>.columns:last-child,#spectacle article .large-up-8>.doc-copy:last-child,#spectacle article .large-up-8>.doc-examples:last-child,#spectacle article .prop-row .large-up-8>.prop-name:last-child,#spectacle article .prop-row .large-up-8>.prop-value:last-child{float:left}#spectacle .large-collapse>.column,#spectacle .large-collapse>.columns,#spectacle article .large-collapse>.doc-copy,#spectacle article .large-collapse>.doc-examples,#spectacle article .prop-row .large-collapse>.prop-name,#spectacle article .prop-row .large-collapse>.prop-value{padding-right:0;padding-left:0}#spectacle .large-collapse .row,#spectacle .large-collapse article .doc-row,#spectacle .large-collapse article .prop-row,#spectacle article .large-collapse .doc-row,#spectacle article .large-collapse .prop-row{margin-right:0;margin-left:0}#spectacle .expanded.row .large-collapse.row,#spectacle .expanded.row article .large-collapse.doc-row,#spectacle .expanded.row article .large-collapse.prop-row,#spectacle article .expanded.doc-row .large-collapse.doc-row,#spectacle article .expanded.doc-row .large-collapse.prop-row,#spectacle article .expanded.doc-row .large-collapse.row,#spectacle article .expanded.prop-row .large-collapse.doc-row,#spectacle article .expanded.prop-row .large-collapse.prop-row,#spectacle article .expanded.prop-row .large-collapse.row,#spectacle article .expanded.row .large-collapse.doc-row,#spectacle article .expanded.row .large-collapse.prop-row{margin-right:0;margin-left:0}#spectacle .large-uncollapse>.column,#spectacle .large-uncollapse>.columns,#spectacle article .large-uncollapse>.doc-copy,#spectacle article .large-uncollapse>.doc-examples,#spectacle article .prop-row .large-uncollapse>.prop-name,#spectacle article .prop-row .large-uncollapse>.prop-value{padding-right:.9868421053rem;padding-left:.9868421053rem}#spectacle .large-centered{margin-right:auto;margin-left:auto}#spectacle .large-centered,#spectacle .large-centered:last-child:not(:first-child){float:none;clear:both}#spectacle .large-pull-0,#spectacle .large-push-0,#spectacle .large-uncentered{position:static;float:left;margin-right:0;margin-left:0}}#spectacle .column-block{margin-bottom:1.3157894737rem}#spectacle .column-block>:last-child{margin-bottom:0}@media print,screen and (min-width:40em){#spectacle .column-block{margin-bottom:1.9736842105rem}#spectacle .column-block>:last-child{margin-bottom:0}}#spectacle #sidebar{padding-top:1.5rem;padding-left:1.5rem;padding-right:1rem;padding-bottom:2rem;border-right:1px solid #eee;background-color:#fe7800;height:100vh;overflow:auto;position:fixed;bottom:0;left:0;top:0;width:300px}#spectacle #sidebar h5{margin:1.5rem 0 .65rem;text-transform:uppercase;color:#000;font-weight:700;font-size:.9rem}#spectacle #sidebar a{display:block;margin:0 0 0;color:#000;white-space:nowrap;overflow:hidden;-o-text-overflow:ellipsis}#spectacle #sidebar a.active{color:#fff;margin:0 0 0}#spectacle #sidebar ul{list-style-type:none;padding:0;margin:0 0 .75em .75rem;line-height:130%}#spectacle #sidebar section>ul{display:none}#spectacle #sidebar section.expand>ul{display:block}#spectacle #sidebar .close-button{opacity:.5}#spectacle .doc-content,#spectacle article .doc-copy,#spectacle article .doc-examples,#spectacle article .panel>h2,#spectacle article .panel>h3,#spectacle article h1.doc-title,#spectacle article>h1,#spectacle article>h2{padding-left:2.25rem!important;padding-right:2.25rem!important}#spectacle .doc-separator,#spectacle article h2{margin-top:2em;padding-top:2em;padding-bottom:2em;border-top:1px solid #e2e2e2}#spectacle #docs{background:#fefefe;overflow:hidden;position:relative}#spectacle #docs .example-box{display:none}@media print,screen and (min-width:64em){#spectacle #docs .example-box{display:block;background-color:#181818;position:absolute;right:0;top:0;bottom:0}}#spectacle article{position:relative}#spectacle article .no-description{color:#222}#spectacle article dt{color:#181818}#spectacle article table.table{width:100%}#spectacle article code{font-size:.9em;border-radius:3px;overflow-wrap:break-word}#spectacle article p:last-child:first-child{margin-bottom:0}#spectacle article h1{margin:2.5rem 0 0;padding-top:.75rem;padding-bottom:.75rem;padding-left:2.25rem;padding-right:2.25rem;border-top:1px solid #e8e8e8;border-bottom:1px solid #e2e2e2;background-color:#f6f6f6}#spectacle article h1.doc-title{margin:0;padding-top:2.15rem;padding-bottom:0;font-weight:700;background:0 0;border:none;color:#464646}#spectacle article h1.doc-title span{display:none;opacity:.65;margin-left:5px;font-weight:400}#spectacle article h2{margin-bottom:0;padding-left:2.25rem;padding-right:2.25rem;padding-bottom:.25rem;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgi…gd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g);background-size:100%;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(0,rgba(255,255,255,.4)),color-stop(100%,rgba(255,255,255,0)));background-image:-moz-linear-gradient(top,rgba(255,255,255,.4),rgba(255,255,255,0));background-image:-webkit-linear-gradient(top,rgba(255,255,255,.4),rgba(255,255,255,0));background-image:linear-gradient(to bottom,rgba(255,255,255,.4),rgba(255,255,255,0))}#spectacle article h3{margin:0 0 .75rem}#spectacle article h1+.panel>h2{margin-top:0;border-top:none}#spectacle article h1+.tag-description+.panel>h2{margin-top:2rem}#spectacle article h1+.panel h3{margin-top:1rem}#spectacle article .panel{position:relative}#spectacle article .prop-row{padding-top:.75em;padding-bottom:.75em;border-top:1px solid #eee}#spectacle article .prop-row.prop-group,#spectacle article .prop-row:first-child{border-top:1px solid #ddd}#spectacle article .prop-row .prop-title{font-weight:700}#spectacle article .prop-row .prop-type{font-weight:400}#spectacle article .prop-row .prop-subtitle{font-weight:400;font-size:80%}#spectacle article .prop-row .prop-name{text-align:right;padding-right:.85rem!important;word-break:break-word}#spectacle article .prop-row .prop-value{padding-left:.85rem!important;word-wrap:break-word}#spectacle article .prop-row.prop-inner{padding-top:.5em;padding-bottom:.5em;font-size:80%}#spectacle article .prop-row.prop-inner .prop-name{color:#222}#spectacle article .prop-row.prop-inner .prop-value>span{display:block}#spectacle article .prop-row.prop-inner .prop-value>span:before{color:#222}#spectacle article .prop-row.prop-inner .prop-value>span:after{color:#222}#spectacle article .prop-row.prop-inner .prop-value>span.json-property-format,#spectacle article .prop-row.prop-inner .prop-value>span.json-property-type{display:inline-block}#spectacle article .doc-row{margin:2rem 0 20px}#spectacle article .doc-examples{padding-left:2.25rem!important;padding-right:2.25rem!important;color:#fefefe;background-color:#181818}#spectacle article .doc-examples h5{color:#fefefe;font-size:1rem;opacity:.8}#spectacle article .doc-examples h5 span{opacity:.5}@media screen and (max-width:63.9375em){#spectacle article .doc-examples:not(:empty){margin-top:1.5rem;padding-top:1.5rem;padding-bottom:.5rem}}#spectacle article .doc-examples code{display:block;margin-bottom:1.5rem;padding:1.5rem;font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:inherit;color:inherit;background-color:transparent;border:none;border-top:1px solid #000;border-bottom:1px solid #404040;border-radius:5px;box-shadow:0 0 200px rgba(0,0,0,.33) inset;word-spacing:normal;white-space:pre-wrap;word-break:normal}#spectacle article .doc-examples tbody tr:nth-child(even){border-bottom:0;background-color:#353a3d}#spectacle article .doc-examples tbody,#spectacle article .doc-examples tfoot,#spectacle article .doc-examples thead{color:#fff;background-color:#181818;border:0}#spectacle article .doc-examples thead{background-color:#181818}#spectacle article .doc-examples .swagger-response-headers{background-color:transparent;border:none;border-top:1px solid #000;border-bottom:1px solid #404040;border-radius:5px;box-shadow:0 0 200px rgba(0,0,0,.33) inset;margin-bottom:1.5rem}#spectacle article .doc-examples .swagger-response-headers thead{color:#fefefe;font-size:1rem;opacity:.8;background-color:transparent;border:none}#spectacle article .doc-examples .swagger-response-headers tbody{border:none;background-color:transparent}#spectacle article .doc-examples .swagger-response-headers tbody tr{background-color:transparent;border-top:1px solid #404040}#spectacle article .powered-by{font-size:80%;color:#cacaca}#spectacle article .powered-by span{color:#f68b1f}#spectacle article .operation .operation-tags{position:absolute;top:0;text-align:right;right:0}#spectacle article .operation .operation-tags .label{cursor:pointer}#spectacle article .operation .operation-tags .label:hover{color:#e6e6e6}@media print,screen and (min-width:64em){#spectacle article .operation .operation-tags{right:50%}}#spectacle article .operation .operation-path{word-break:break-all}#spectacle article .security-definition-description{margin-top:1.5rem;margin-bottom:.5rem}#spectacle article .security-definition-scope-description{color:#222;margin-bottom:.5rem}#spectacle article .definition .doc-examples h5{margin-top:-1rem}#spectacle article .definition .doc-copy>section{margin-bottom:1rem}#spectacle article .definition .doc-copy>section>.json-property-type{display:none}#spectacle article .definition dl dt{font-weight:400}#spectacle article .definition .json-property-name{font-weight:700}#spectacle article .hljs{line-height:1.25;overflow-x:auto;padding:1.5rem;margin-bottom:1rem;border:none}#spectacle .drawer-layout .drawer{box-shadow:0 0 10px rgba(24,24,24,.5);transition:transform .2s ease;backface-visibility:hidden}#spectacle .drawer-layout .drawer.slide-left{transform:translateX(-250px)}#spectacle .drawer-layout .drawer.slide-right{transform:translateX(250px)}#spectacle .drawer-layout .drawer .drawer-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background-color:rgba(254,254,254,.25)}@media print,screen and (min-width:64em){#spectacle .drawer-layout .drawer.slide-left{transform:none;margin-left:-300px}#spectacle .drawer-layout .drawer.slide-right{transform:none;margin-left:300px}#spectacle .drawer-layout .drawer .drawer-overlay{display:none}}@media print,screen and (min-width:64em){#spectacle .drawer-layout.drawer-slide-left-large .drawer{margin-left:-300px}#spectacle .drawer-layout.drawer-slide-left-large .floating-menu-icon{opacity:0}#spectacle .drawer-layout.drawer-slide-right-large .drawer{margin-left:300px}#spectacle .drawer-layout.drawer-slide-right-large .floating-menu-icon{opacity:0}}#spectacle .drawer-layout.drawer-open .floating-menu-icon{opacity:0}#spectacle .drawer-layout .floating-menu-icon{position:fixed;top:.75rem;right:.75rem;background-color:rgba(24,24,24,.75);padding:.65rem;z-index:1;border-radius:5px;transition:opacity .2s linear}#spectacle .drawer-layout .floating-menu-icon .hamburger{position:relative;display:inline-block;vertical-align:middle;width:20px;height:16px;cursor:pointer}#spectacle .drawer-layout .floating-menu-icon .hamburger::after{position:absolute;top:0;left:0;display:block;width:100%;height:2px;background:#fefefe;box-shadow:0 7px 0 #fefefe,0 14px 0 #fefefe;content:''}#spectacle .drawer-layout .floating-menu-icon .hamburger:hover::after{background:#cacaca;box-shadow:0 7px 0 #cacaca,0 14px 0 #cacaca}#spectacle .hljs{display:block;overflow-x:auto;padding:.5em;background:#23241f}#spectacle .hljs,#spectacle .hljs-subst,#spectacle .hljs-tag{color:#f8f8f2}#spectacle .hljs-emphasis,#spectacle .hljs-strong{color:#a8a8a2}#spectacle .hljs-bullet,#spectacle .hljs-link,#spectacle .hljs-literal,#spectacle .hljs-number,#spectacle .hljs-quote,#spectacle .hljs-regexp{color:#ae81ff}#spectacle .hljs-code,#spectacle .hljs-section,#spectacle .hljs-selector-class,#spectacle .hljs-title{color:#a6e22e}#spectacle .hljs-strong{font-weight:700}#spectacle .hljs-emphasis{font-style:italic}#spectacle .hljs-attr,#spectacle .hljs-keyword,#spectacle .hljs-name,#spectacle .hljs-selector-tag{color:#f92672}#spectacle .hljs-attribute,#spectacle .hljs-symbol{color:#66d9ef}#spectacle .hljs-class .hljs-title,#spectacle .hljs-params{color:#f8f8f2}#spectacle .hljs-addition,#spectacle .hljs-built_in,#spectacle .hljs-builtin-name,#spectacle .hljs-selector-attr,#spectacle .hljs-selector-id,#spectacle .hljs-selector-pseudo,#spectacle .hljs-string,#spectacle .hljs-template-variable,#spectacle .hljs-type,#spectacle .hljs-variable{color:#e6db74}#spectacle .hljs-comment,#spectacle .hljs-deletion,#spectacle .hljs-meta{color:#75715e} \ No newline at end of file diff --git a/swagger/cardholdersApi.yaml b/swagger/cardholdersApi.yaml new file mode 100644 index 0000000..0289dba --- /dev/null +++ b/swagger/cardholdersApi.yaml @@ -0,0 +1,7556 @@ +## +## Copyright Gallagher Group Ltd 2020 All Rights Reserved +## THIS IS PROPRIETARY DOCUMENTATION SOURCE CODE OF +## Gallagher Group Research and Development +## Hamilton, New Zealand +## + +swagger: '2.0' +openapi: '3.0' + +schemes: + - https +produces: + - application/json +consumes: + - application/json + +info: + version: 1.0.0 + title: "Command Centre REST API: Cardholders" + + description: | + This document describes how you can use the Command Centre REST API to view and manage + cardholders and their cards, mobile credentials, access groups, personal data, roles, + competencies, personal data, and lockers. + + It has companion documents describing the [PIV](piv.html), [Alarms and Events](events.html), and + [Access items](rest.html) APIs. + + It assumes some familiarity with HTTP and REST interfaces. Before you start + developing against this interface, you should also read the [Authentication + section](events.html#authentication) of the accompanying Alarms and Events API + documentation to learn how to format your queries, and Command Centre's Configuration + Client online help to learn how to ready the server to receive them (search for 'REST + API'). + + Each section contains a list of use cases intended as quick solutions for simple tasks + or a how-to to get you started toward your particular goal. However they also serve + as a good introduction to the API if you step through them using a web browser, + starting at `/api` on your Command Centre server. To do that, you will need a browser + plugin that lets you set an Authorization HTTP header, and a JSON formatter to + prettify the results documents. Search the Configuration client's online help for + 'test REST API' for guidance. You will also need to prepare Command Centre, as above. + + ### Licensing + + All of the API calls described here except visitor management are available with the + RESTCardholders licence. Visitor management calls need both RESTCardholders and + VisitorManagement. Without RESTCardholders, lockers and locker banks are available with the + RESTStatus licence, and partially 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. + + #### Cardholder API changes on the roadmap + + * You will be able to create, delete, and change the the basic configuration of access groups, + competencies, PDFs, and card types. + + * 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, but since it was not documented, none should. + + * POTENTIALLY BREAKING CHANGE: PATCH methods may return "200 Success" instead of "204 No + Content" and will contain 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's visits and escort. + + * "Revealable" access levels on PDFs. + + * POTENTIALLY BREAKING CHANGE: a future version will sort its search results differently if your query does + not include a parameter to override the default. + + * 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. + + * 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. + + #### Cardholder API changes in 9.00 + + * You can search for cardholders by the access zone they are in. + + * GovPass cards have two new fields, `visitorContractor` and `ownedBySite`. + + #### Cardholder API changes in 8.90 + + * 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](#operation--api-visits--id--visitors--visitorId--patch) 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. + + #### Cardholder 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](#tag-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. + + #### Cardholder API changes in 8.70 + + * 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 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. + + #### Cardholder API changes in 8.60 + + * 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. + + #### API changes in 8.50 + + * The server property that turns off [client certificate checking](#authentication) changed. + * You can manage operators by setting their operator group memberships and logon credentials. + * You can view [operator groups](#tag-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. + + #### API changes in 8.40 + + * 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](#definition-Access-group-detail). + + * 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 + + * Cardholder [change tracking](#tag-Cardholder-changes) 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. + + #### API changes in 8.20 + + * A new field `update_location` on a cardholder gives a link to a + [POST](#operation--api-cardholders--id--update_location-post) that changes his or her current + location (access zone). + + * Lockers and locker banks are now visible with the RESTStatus licence, including whether a + locker is available or assigned. 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](#definition-Cardholder-relationship) 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](#operation--api-lockers--id--open-post) to a locker to open it. + + * You can use the `fields` query parameter to select the fields you receive from locker bank + [summary](#operation--api-locker_banks-get) and + [details](#operation--api-locker_banks--id--get) GETs. The [use + cases](#opening-a-locker) section shows how. + + #### API changes in 8.10 + + All changes contain the text "8.10". + + * A [card types API](#operation--api-card_types-assign-get) that returns the card types your + operator is able to use when assigning cards to cardholders. + * A [PDF API](#operation--api-personal_data_fields-get) that returns the PDF definitions your + operator is able to see. + + * You can change the [notifications flag](#definition-Cardholder-PATCH-example) on a + cardholder's PDF. + + * Certificates and biometric data are available on PIV cards. + + ### Efficiency tips + + * When searching for a cardholder (or any other object) by name or PDF value, if you have the + exact string, put it in quotes `"..."`. It is still a case-insensitive search but it attempts + to match the entire string. Without the quotes Command Centre will perform a substring + search, which is slower and may return more results than you need. In one test, a substring + search of half a million cardholders took more than ten seconds, but with quotes it took a + tenth of a second. That is a 100x speedup. If the server believes you have omitted the + quotes in error, it will include a polite reminder in its results. + + * When downloading a significant number of items, sort by ID. + + * Set your page size as large as your client can handle. The default was 100 before 8.70; using + the `top` parameter to take it to 1000 or more gives _much_ better throughput. + + To give you an idea of the effect of setting `sort` and `top`, here are some timings for a + download of 200,000 cardholders from CC 8.40: + + | Sorting | Pagination | Time + | ------------ | -------------- | -------- + | Default (by name) | Default (100) | 28 minutes + | `sort=id` | Default (100) | 5 minutes + | Default (by name) | `top=1000` | 3.5 minutes + | `sort=id` | `top=1000` | 1 minute + + * In version 8.00 or later, use the [`fields` parameter](#topic-Field-specifiers) to add fields + to the results of a search, saving you GETting the details page. + + For example, to see the names, PDFs, and access groups of all your cardholders: + + `GET /api/cardholders?sort=id&top=10000 &fields=firstname,lastname,personalDataFields,accessGroups` + + It adds to the payload size, though, so only do this if you are certain that the search will + not return too many cardholders you are not interested in. + + * If you are writing an integration that synchronises users from a source of truth to Command + Centre, see the 'Synchronising a user directory' use case in the Cardholders section. + + * If you must get an object's details page, and you are on v8.00 or later, using the [`fields` + parameter](#topic-Field-specifiers) will still save work. Be explicit with what you want: do + not use `fields=defaults` unless you need every field that 'defaults' gives you and you + accept that we may add fields to the defaults in future. + + * When creating cardholders, assign their cards, groups, and other attributes in the POST. This is + significantly quicker than sending a handful of PATCHes chasing after your POST, and if you + are interrupted, you will not end up with a partially-configured cardholder. + + * When modifying a cardholder, do all your operations in one PATCH. This is much quicker than + doing it in several smaller PATCHes. + + ### 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. + + ### 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. + +x-spectacle-topics: + Documentation suite: + $ref: "indexApi.yaml#/x-spectacle-topics/Documentation suite" + + Forward compatibility (HATEOAS): + $ref: "eventsApi.yaml#/x-spectacle-topics/Forward compatibility (HATEOAS)" + + Operator privileges: + $ref: "eventsApi.yaml#/x-spectacle-topics/Operator privileges" + + Field specifiers: + description: | + You can specify the fields you want in the results of a query. This lets you: + + - save calls by adding more fields to search results, which are terse by default + - save bandwidth by putting fewer fields on detail pages, which are verbose by default, and + - retrieve more data by adding fields to detail pages that are not normally there, such as + an item's status, short name, and notes. + + You do this using the query parameter `fields` to the search, detail, and updates pages. It + takes a comma-separated list of field names. They are case-sensitive: copy them carefully + from this document. The special field name `defaults` adds in the fields the server would + have sent had you not set the `fields` parameter at all. + + Example GETs: + + - `/api/cardholders?name=Smith&fields=defaults,cards,competencies` will list all cardholders + with 'Smith' in the first or last name, showing the fields you normally get on a search + page plus their cards and competencies. + + - `/api/access_groups/123?fields=children,cardholders` will show just the child groups of + access group 123 and a link to retrieve its cardholders - nothing else. + + - `/api/outputs?fields=name,shortname,id` will show the name, short name, and ID of your outputs. + - `/api/access_zones?fields=name,doors` will show the name and attached doors of your access + zones. + + - `/api/alarms?fields=time,message,details` will return the time, message text, and details + text of your alarms. Not only does that save a lot of data by removing default fields, + but it adds the details field which does not normally come out of an alarm search. + + Each controller has different optional fields - see their sections under Operations. + +x-common-blocks: + server: &SERVER + type: string + example: "ruatoria.satellite.int" + description: | + 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. + 409ch: &409CH + description: | + The cardholder is locked for editing by another operator. The body of the response will + tell you which operator is holding the lock. + ccontroller_name_irrelevant: &CONNECTEDCONTROLLER + type: object + description: | + 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. + properties: + name: {type: string, example: "Third floor C6000"} + href: + type: string + format: url + example: "https://localhost:8904/api/items/508" + description: | + 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. + id: + type: string + description: | + An alphanumeric identifier, unique to the server. This is the ID to use in the `source` + parameter of [event filters](events.html#operation--api-events-get). + example: "634" + ccontroller_name_still_irrelevant: &CONNECTEDCONTROLLER_BRIEF + type: object + description: | + 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. + + visit_name: &VISIT_NAME + type: string + example: "Greeter, Ginger" + description: | + 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! + v_desc: &VISIT_DESCRIPTION + type: string + example: "Initial scoping" + description: | + 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. + + v_recept: &VISIT_RECEPTION + type: object + description: | + 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"` + + example: + name: "Main lobby" + href: "https://localhost:8904/api/receptions/937" + + v_vt: &VISIT_VISITORTYPE + type: object + description: | + 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](#operation--api-access_groups-get) or the visitor + management configuration on a + [division](events.html#operation--api-divisions--operation--get). 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'. The 'Modify Access Control' privilege + 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. + example: + href: "https://localhost:8904/api/divisions/2/v_t/925" + accessgroup: + name: "Visitor group 1" + href: "https://localhost:8904/api/access_groups/925" + + v_host: &VISIT_HOST + type: object + description: | + 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. + example: + name: "Ginger Greeter" + href: "https://localhost:8904/api/cardholders/526" + v_frontil: &VISIT_FROM_UNTIL + type: string + description: | + 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](#dates-and-times). + + 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. + format: date-time + example: "1971-03-08T14:35:00Z" + v_locn: &VISIT_LOCATION + type: string + description: Free text. Entirely optional in the POST and PATCH. + example: "Gather in Ginger's office" + v_vags: &VISIT_AGS + type: array + description: | + 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 + access group hrefs. 8.50 does not accept the same kind of visitor access group + hrefs here that it sends, so 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 such as 'Modify Access Control' 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'. + + example: + - accessGroup: + name: "Access group 22" + href: "https://localhost:8904/api/access_groups/926" + v_ors: &VISIT_ORS + type: array + description: | + 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](#operation--api-visits--id--visitors--visitorId--patch) to change their state (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 by the server's language. `type` comes from a fixed enumeration. + + The `invitation` string is opaque: please do not interpret it. + + A visit with no visitors on it 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 + hrefs to cardholders. 8.50 does not accept the same kind of visitor hrefs here + that it sends, so this is the other reason you cannot copy a visit by POSTing back the JSON + from a GET. Don't 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 one or two arrays called `add` and `remove`. 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 such as 'Modify Access Control' that allows both + viewing the cardholders and changing their access groups memberships, otherwise you will + be told 'Access denied when writing to one or more fields'. + + items: + type: object + properties: + cardholder: + description: | + A block containing the name and cardholder href of the visitor. + href: + type: string + format: url + description: | + An href unique to this cardholder on this visit. You can PATCH it to change the + visitor's state. + + New to 8.90. + invitation: + type: string + description: | + 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. + status: + type: object + description: | + The cardholder's status on this visit, in human-readable and machine-readable forms. + + Also new to 8.90. + properties: + value: + type: string + description: | + A human-readable version of the visitor's status, varying with the version of + Command Centre and the server's locale. + type: + type: string + description: | + The visitor's current state taken from a fixed list of strings. New visitors have a + starting state of 'expected'. + enum: + [expected, signingIn, signedIn, onSite, expectedBack, departed, cancelled ] + + example: + - 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" + +##################################################################################### +##################################################################################### +##################################################################################### +tags: +- name: Cardholders + description: | + 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 won't 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. + + ### Use cases + + #### Searching for cardholders by name + 1. `GET /api`. + 2. Follow the link at `features.cardholders.cardholders.href` + [↪](#operation--api-cardholders-get), 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` + [↪](#operation--api-cardholders-get). 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](#tag-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.00 or earlier: follow the link at `features.items.items.href` + [↪](events.html#operation--api-items-get), 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. + + 2. v8.10 or later: follow the link at `features.personalDataFields.personalDataFields.href` + [↪](#operation--api-personal_data_fields-get), appending the query `name="employee_id"`. + This will return every PDF in the system with that name (there should only be one since PDFs + must have different names). + + 3. 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', assuming you had + 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` + [↪](#operation--api-cardholders-get), adding a query separator and + `pdf_=`, without the angle brackets. + + The results will only include cardholders who have *\* as a substring of + the Personal Data Field with ID *\*. Again, the search is + case-insensitive and you should bookend *\* 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 *\* 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](#operation--api-cardholders-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` + [↪](#operation--api-card_types-assign-get). + + 4. Compose a JSON body using [this example](#definition-Cardholder-POST-example) and + [this detail](#definition-Cardholder-detail), then + [POST](#operation--api-cardholders-post) it to the href at + `features.cardholders.cardholders.href` [↪](#operation--api-cardholders-post) 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](#operation--api-cardholders--id--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](#definition-Cardholder-PATCH-example) 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](#operation--api-cardholders--id--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](#tag-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](#definition-Cardholder-PATCH-example). + 3. [PATCH](#operation--api-cardholders--id--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` [↪](#operation--api-card_types-get) 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. + 4. PATCH the cardholder's href according to the [cardholder patch + schema](#definition-Cardholder-PATCH-example). + + #### Deleting a cardholder's PDF value + 1. Find the URL of your cardholder using one of the processes above. + 4. [PATCH](#operation--api-cardholders--id--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` + [↪](rest.html#operation--api-access_zones--update_cardholder_location-get) in the results of + `GET /api`. + 1. Find your cardholder using one of the processes above. If you are using + [search](#operation--api-cardholders-get), add `updateLocation` to the `fields` parameter to + save you having to GET their details page later. + 2. Send an [HTTP POST](#operation--api-cardholders--id--update_location-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: + + 3. For each access zone, GET its href and look in its `doors` block. + + #### Finding recently-created cardholders + + This is another function of the [Cardholder changes](#tag-Cardholder-changes) call, described + there. + + However to do that you need to make an API call _before_ creating the cardholders. If the + cardholders 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](#operation--api-cardholders-get) and + [details](#operation--api-cardholders--id--get) operations' `fields` parameter and the [change + tracking](#operation--api-cardholders-changes-get) 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--api-cardholders--id--get) 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. + +- name: Cardholder changes + description: | + This feature keeps you up to date with cardholder changes. It works for changes 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](eventsApi.yaml) quicker and more + flexible for those tasks. + + ### Recommended use + + 1. First, get a bookmark at the end of Command Centre's list of changes as follows: + + 1a. `GET /api` + + 1b. Follow the link at `features.cardholders.changes` [↪](#operation--api-cardholders-changes-get). + + 1c. Take note of the href in the `next` block. This is your bookmark. Later on you will + ask for all the changes that happen after this point. + + 1. If you are filling a user directory from Command Centre, download all cardholders using [this + advice](#downloading-all-cardholders). Here is a reminder: + + 2a. Using the results of `GET /api` again, follow the link at + `features.cardholders.cardholders` [↪](#operation--api-cardholders-get), 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. + + 1. 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. + + 1. 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`. + + 1. 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. + + 1. Restart your polling loop using the link in `next.href`. + + ### Use case: discovering recently-created cardholders. + + The first thing you need to do is get a link from the changes API call _before_ creating the + cardholders. 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 by removing + everything else with `fields=newValues`. + + ### 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 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. + +- name: Access groups + + description: | + + These methods give you read access to the access groups in Command Centre. You can search for + groups, see their lineage, list their cardholder members, and (in 8.40 or later) see how they + affect those members. + + Your REST operator will need the 'View' or 'Edit Access Groups' operator privileges for the + GETs. + + They also provide the links you need to manage memberships. Those links are hrefs to + cardholders, to which you would send [PATCH](#definition-Cardholder-PATCH-example) requests + containing your updates. Your REST operator will need 'Edit Cardholder' 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` + [↪](#operation--api-access_groups-get) (adding search terms, and setting `top` high to save + pagination). + 3. Find your access group in the results. + + 4. The link at `cardholders` [↪](#operation--api-access_groups--id--cardholders-get) 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` + [↪](#operation--api-access_groups-get). 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. + +- name: Competencies + description: | + 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. + +- name: Card types + description: | + The card type API exists so that you can 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 it to a cardholder, so version 8.10 of the API added a page that returns the card + types that your operator is allowed to assign to cardholders. 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](piv.html). + +- name: Lockers + description: | + + 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. + + This means: + + - everything about a locker bank is in one place: lockers are components of a bank (there is + no separate locker API), and its detail page shows all cardholder assignments, and + + - you cannot use the API to create or configure lockers or banks. There is no mention of 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 actually updating. The locker bank API supplies that URL. + + ### 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` + [↪](#operation--api-locker_banks-get), 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](#definition-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. + + 1. Find the href of the cardholder using the [cardholders + API](#operation--api-cardholders-get). + + 3. [PATCH](#operation--api-cardholders--id--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 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. + + 1. `GET /api` + 2. Follow the link at `features.lockerBanks.lockerBanks.href` + [↪](#operation--api-locker_banks-get), 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`. If your operator is able to override that locker it will contain a field called + `href`, giving the URL you need to [POST](#operation--api-lockers--id--open-post) to open the + locker. + +- name: Operator groups + + description: | + + 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` + [↪](#operation--api-operator_groups-get) (adding search terms, and setting `top` high to save + pagination). + 3. Find your operator group. + + 4. The link at `cardholders` returns the cardholders who have membership of your group. + + 5. 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. + +- name: PDF definitions + description: | + + 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. + + ### 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](#definition-Cardholder-detail). 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 `fields=accessGroups` added to the query. + 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` + [↪](#operation--api-personal_data_fields-get), 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](#definition-Cardholder-PATCH-example) with a field named + after the PDF following an '@'. + +- name: Receptions + description: | + 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` + 1. Follow the link at `features.receptions.receptions.href` [↪](#operation--api-receptions-get) + 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. + 1. Find the reception you're after and use its href in a visit. + +- name: Redactions + description: | + 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. + +- name: Roles + description: | + + 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 you can change the cardholder's supervisor but not + who the cardholder supervises. + + ### Use case: searching for a role by name and assigning a relationship + + 1. `GET /api` + 1. Follow the link at `features.roles.roles.href` [↪](#operation--api-roles-get) after adding + search terms such as `name="supervisor"`. + 1. Find the href of the role with which you want to link your two cardholders. + 1. Find the href of the cardholder who will perform this role (the supervisor). + 1. Find the href of the cardholder for whom he or she will perform this role (the supervised). + 1. [PATCH](#operation--api-cardholders--id--patch) the second cardholder (the supervised) with + the href of the first cardholder (the supervisor) and the href of the role. Here is an + [example PATCH](#definition-Cardholder-PATCH-example). + +- name: Visits + description: | + + Visits are new to 8.50. + + ### Use case: finding a visit by name + 1. `GET /api` + 1. Follow the link at `features.visits.visits.href` [↪](#operation--api-visits-get) after adding + search terms such as `name=Ginger Greeter`. A site typically has many visits, so add + `top=1000` for efficiency's sake. + 1. 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](#tag-Receptions) at which your visitors will arrive. + 1. Look at the visitor management configuration for your reception's + [division](events.html#definition-Division). + 1. 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. + 1. Pick (or create) at least one cardholder for your visitor or visitors. + 1. Build a [JSON payload](#definition-Visit) containing all that. + 1. POST to the link at `features.visits.visits.href` [↪](#operation--api-visits-post) (in the + payload of the `GET /api` you did for the first step). + +securityDefinitions: + "API key": + $ref: "eventsApi.yaml#/securityDefinitions/API key" + +security: + - "API key": [] + +########################################################################################### +########################################################################################### + +parameters: + sort: + name: sort + in: query + type: string + required: false + enum: [ id, name, -id, -name] + description: | + 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. 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. + + top: + name: "top" + in: query + required: false + type: integer + minimum: 1 + description: | + Limits the results to no more than this many items per page. + + Older versions of Command Centre returned 100 items per page. 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 return 1000 items per request. This is about where a graph of + performance versus page size begins to level out. + + name: + name: "name" + in: query + type: string + description: | + 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. + + pdf_fields: + name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, description, division, serverDisplayName, type, default, required, + unique, sortPriority, accessGroups, regex, regexDescription, defaultAccess, + operatorAccess, notificationDefault + ] + description: | + 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. + + division: + name: "division" + in: query + required: false + type: array + items: + type: string + description: | + 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. + + List the IDs of the divisions you are interested in separated by 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. + + description: + name: "description" + in: query + required: false + type: string + description: | + 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. + + reception_fields: + name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, name, description, division, serverDisplayName, defaultVisitorType, notes] + description: | + 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. + + requested_by: + name: "requested_by" + in: query + required: false + type: string + default: [ none ] + description: | + 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. + + visit_fields: + name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, name, description, division, serverDisplayName, notes, reception, visitorType, host, from, until, location, badgeText, visitorAccessGroups, visitors ] + description: | + 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. + +#################################################################################################### +#################################################################################################### +#################################################################################################### +definitions: + + Cardholder search: + description: An array of cardholder summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of cardholder summaries. + items: { $ref: '#/definitions/Cardholder summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/cardholders?skip=61320" + + Cardholder summary: + description: | + 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. + properties: + href: + type: string + format: url + description: | + A link to a [cardholder detail](#definition-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." + example: "https://localhost:8904/api/cardholders/325" + id: + type: string + readOnly: true + description: | + An alphanumeric identifier, unique to the server. No API calls use cardholder IDs. + + Deprecated. Use the href instead. + example: "325" + firstName: + type: string + example: "Algernon" + lastName: + type: string + example: "Boothroyd" + shortName: + type: string + maxLength: 16 + description: "If you supply a string that is too long when creating or updating a cardholder, Command Centre will truncate it." + example: "Q" + description: + type: string + example: "Quartermaster" + authorised: + description: | + 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. + + type: boolean + example: true + + Cardholder detail: + description: | + [/api/cardholders/{id}](#operation--api-cardholders--id--get) returns one of these, and you + submit [parts of one](#definition-Cardholder-POST-example) 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. + + allOf: + - type: object + properties: + lastSuccessfulAccessTime: + type: string + format: date-time + readOnly: true + description: The date and time of the last successful card event. + example: "2004-11-18T19:21:52Z" + lastSuccessfulAccessZone: + type: object + readOnly: true + description: | + The last zone the cardholder entered. 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`. + + example: + href: "https://localhost:8904/api/access_zones/333" + name: "Twilight zone" + serverDisplayName: + <<: *SERVER + division: + type: object + description: | + The division containing this cardholder. You must send this when creating a + cardholder. + example: + href: "https://localhost:8904/api/divisions/2" + "@Student ID": + description: | + An example Personal Data Field value. + + All PDFs except images appear at the top level of a cardholder in a field + named after the PDF with a leading '@'. + + Date PDFs will contain a date-time with the time part set to midnight. + + Image PDFs appear as a block named after the PDF with a leading '@' + containing an href. GETting that href will return the image data with an + appropriate content-type header. + + Put your PDF values here when creating or modifying a cardholder. Note that to hold a + PDF value the cardholder must be a member of an access group that has that PDF + attached to it. In this example, being a member of the special projects group has + granted our Mr. Boothroyd the student ID and photo PDFs. + + To set an image PDF, Base64-encode the image and send it as a string. There is one in + the [cardholder POST example](#definition-Cardholder-POST-example). + + Date PDFs are only accurate to a day, so version 8.75 and later will ignore the time + and time zone components if you send them. 8.70 and earlier will first perform a + timezone correction then convert it to UTC before truncating the time, which might + shift the date back a day if you specified a positive timezone offset. To be sure + that does not happen on a server older than 8.75, put `T00:00:00Z` on the end of your date. + + example: "8904640" + disableCipherPad: + type: boolean + example: false + description: | + 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. + usercode: + type: string + writeOnly: true + example: "numeric, and write-only" + description: | + 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. + operatorLoginEnabled: + type: boolean + example: true + description: | + 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. + operatorUsername: + type: string + example: qmaster + description: | + The name that the operator uses when logging in to the interactive clients. Absent if blank. + + Send the empty string `""` to clear it. + operatorPassword: + type: string + example: write-only + description: | + 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. + operatorPasswordExpired: + type: boolean + example: false + description: | + If true, the interactive clients will request a new password the next time this person + logs in. + windowsLoginEnabled: + type: boolean + example: true + description: | + If true, and they have a Windows username, this user can log in using Windows + integrated authentication. + windowsUsername: + type: string + example: misix.local\qmaster + description: | + 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. + personalDataDefinitions: + type: array + description: | + 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). + items: + {$ref: '#/definitions/Cardholder PDF'} + example: + - '@Student ID': + href: "https://localhost:8904/api/cardholders/325/personal_data/2356" + definition: {href: "https://localhost:8904/api/personal_data_fields/2356", + name : "Student ID", id: "2356", type: "string"} + value: "8904640" + - '@Photo': + href: "https://localhost:8904/api/cardholders/325/personal_data/2369" + definition: {href: "https://localhost:8904/api/personal_data_fields/2369", + name : "Photo", id: "2369", type: "image"} + value: { href: "https://localhost:8904/api/cardholders/325/personal_data/2369" } + + cards: + type: array + description: | + 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. + items: {$ref: '#/definitions/Cardholder card'} + accessGroups: + type: array + description: | + 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'. + items: {$ref: '#/definitions/Cardholder access group'} + operatorGroups: + type: array + description: | + All the cardholder's operator group memberships. Added in 8.50. + items: {$ref: '#/definitions/Cardholder operator group'} + competencies: + type: array + description: | + Every competency a cardholder possesses. There is quite a lot there: see the schema + definition for detailed explanation. + items: {$ref: '#/definitions/Cardholder competency'} + edit: + type: object + format: url + readOnly: true + description: | + Reserved for internal use. It is a link to [another + call](#operation--api-cardholders--id--edit-get) that helps interactive administrative + clients. Do not send it when creating or editing a cardholder. + example: + href: "https://localhost:8904/cardholders/325/edit" + updateLocation: + type: object + readOnly: true + description: | + Link to [POST](#operation--api-cardholders--id--update_location-post) to when you want + to update the location of this cardholder. Added in 8.20. + example: + href: "https://localhost:8904/api/cardholders/402/update_location" + notes: + type: string + description: | + 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. + + example: "" + notifications: + type: object + description: |- + 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'. + example: + enabled: true + from: "2017-10-10T14:59:00Z" + until: "2017-10-17T14:59:00Z" + relationships: + type: array + items: {$ref: '#/definitions/Cardholder relationship'} + description: | + 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. + lockers: + type: array + description: | + All the cardholder's locker assignments. + items: {$ref: '#/definitions/Cardholder locker'} + + elevatorGroups: + type: array + description: | + The cardholder's elevator group properties. They may have one per elevator group. + + Added in 8.50. + items: {$ref: '#/definitions/Cardholder elevator group'} + + updates: + type: object + description: | + 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](#tag-Cardholder-changes) methods are a far more efficient way of monitoring + your cardholders. + redactions: + type: array + description: | + 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. + items: {$ref: '#/definitions/Cardholder redaction'} + + - $ref: "#/definitions/Cardholder summary" + + Cardholder POST example: + description: | + 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, and two lockers. + + There are plenty more fields than shown in this example. For a complete list please see the + schema for the detailed [cardholder object](#definition-Cardholder-detail) that you receive + from a cardholder href. + + required: [ division ] + properties: + firstName: + type: string + description: You must supply either this or the last name when creating a cardholder. + example: "Algernon" + lastName: + type: string + description: You must supply either this or the first name when creating a cardholder. + example: "Boothroyd" + shortName: + type: string + example: "Q" + description: + type: string + example: "Quartermaster" + authorised: + description: | + 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. + type: boolean + example: true + division: + type: object + description: | + Mandatory when creating any cardholder. In this example, we want all students + in division 5387. + example: + href: "https://localhost:8904/api/divisions/5387" + "@email": + description: | + An example PDF value. In this example, access group 352 must include a PDF + called 'email' otherwise this will appear to have no effect. + + type: string + example: "user@sample.com" + "@headshot": + description: | + 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/`. + type: string + example: "/9j/4A...==" + personalDataDefinitions: + description: | + 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. + type: array + items: {$ref: '#/definitions/Cardholder PDF'} + example: + - "@email": {notifications: true} + cards: + description: | + 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. + type: array + items: {$ref: '#/definitions/Cardholder card'} + example: + - type: {href: "https://localhost:8904/api/card_types/600"} + pin: "153624" + - type: {href: "https://localhost:8904/api/card_types/654"} + number: "Nick's mobile" + invitation: + email: "nick@example.com" + mobile: "02123456789" + singleFactorOnly: true + + accessGroups: + type: array + items: {$ref: '#/definitions/Cardholder access group'} + description: | + 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. + example: + - accessgroup: + href: "https://localhost:8904/api/access_groups/352" + from: "2019-01-01" + operatorGroups: + type: array + items: {$ref: '#/definitions/Cardholder operator group'} + description: | + Here you can add the operator groups necessary to give your new cardholder the + software access he or she needs. Added in 8.50. + example: + - operatorgroup: + href: "https://localhost:8904/api/operator_groups/523" + competencies: + type: array + items: {$ref: '#/definitions/Cardholder competency'} + description: | + In this example we are giving our new cardholder a disabled competency, set to enable in + January 2019. + example: + - competency: + href: "https://localhost:8904/api/competencies/2354" + enabled: false + enablement: "2019-01-01" + + notes: + type: string + example: "" + notifications: + type: object + description: "You can set or update any of the three fields in this block." + example: + enabled: true + from: "2017-10-10T14:59:00Z" + until: "2017-10-17T14:59:00Z" + relationships: + type: array + items: {$ref: '#/definitions/Cardholder relationship'} + description: | + 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. + example: + - role: + href: "https://localhost:8904/api/roles/5396" + cardholder: + href: "https://localhost:8904/api/cardholders/5398" + lockers: + type: array + description: | + 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. + items: {$ref: '#/definitions/Cardholder locker'} + example: + - locker: + href: "https://localhost:8904/api/lockers/3456" + - locker: + href: "https://localhost:8904/api/lockers/3457" + + elevatorGroups: + type: array + description: | + 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. + items: {$ref: '#/definitions/Cardholder elevator group'} + example: + - elevatorGroup: + href: "https://localhost:8904/api/elevator_groups/635" + accessZone: + href: "https://localhost:8904/api/access_zones/637" + - elevatorGroup: + href: "https://localhost:8904/api/elevator_groups/639" + enableCodeBlueFeatures: true + + Cardholder Update Location POST example: + description: | + This is an example of a POST you could use to move a cardholder to a target access zone. + + properties: + accessZone: + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/access_zones/412" + description: | + The href of the access zone into which you want to move your cardholder. This example + came from the [access zones + controller](rest.html#operation--api-access_zones-update_cardholder_location-get), but + you can also use an href from the [items controller](events.html#tag-Items) if you do + not have a RESTStatus or (in 8.60) RESTOverrides licence. + + Cardholder PATCH example: + description: | + 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, groups, and relationships. + + All the cardholder fields you can supply are described in the [cardholder + detail](#definition-Cardholder-detail). + + This example: + + - sets 'authorised' true, + - sets a PDF holding an employee ID, + - changes the notification flags on two PDFs, + - 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. + + This is also the method you use for changing a cardholder's name, description, notes, user + code, etc. + + In addition to those fields, this PATCH format accepts five arrays: 'cards', + 'accessGroups', 'competencies', 'relationships', and (in 8.50) 'operatorGroups'. + + properties: + authorised: + type: boolean + example: true + description: | + You can modify [all the fields](#definition-Cardholder-detail) on a cardholder, provided + you have the necessary privileges, + "@employeeId": + type: string + example: THX1139 + description: | + 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 he or she is not a member of + the access group that grants that PDF. In this example, one or both of access groups 352 + and 124 are attached to 'employeeId'. + personalDataDefinitions: + description: | + This is how you set and unset the notifications flags on PDFs. Presumably this cardholder + wants to receive notifications at the email address stored in the 'email' PDF rather than + by SMS. + type: array + items: {$ref: '#/definitions/Cardholder PDF'} + example: + - "@email": {notifications: true} + - "@cellphone": {notifications: false} + + cards: + type: object + description: | + This object can contain three arrays, named 'add', 'update', and 'remove'. Every element + you put in those arrays should be in the [card schema](#definition-Cardholder-card). + + 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. + + example: + add: + - type: + href: "https://localhost:8904/api/card_types/354" + pin: "153624" + - type: + href: "https://localhost:8904/api/card_types/600" + number: "Jock's iPhone 8" + status: { value: "Pending sign-off"} + invitation: { email: "jock@example.com" } + update: + - href: "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d" + issueLevel: 2 + until: "" + status: { value: "Stolen" } + pin: "153624" + remove: + - href: "https://localhost:8904/api/cardholders/325/cards/77e8affe7c7e4b56" + status: { value: "Lost" } + + accessGroups: + type: object + description: | + 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](#definition-Cardholder-access-group). + + 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. + + Each element of the 'update' array will need an href identifying the membership to update, + and one or both of the 'from' and 'until' date-times containing new values (you cannot + change the group--just the dates). 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. + + example: + add: + - accessGroup: + href: "https://localhost:8904/api/access_groups/352" + - accessGroup: + href: "https://localhost:8904/api/access_groups/124" + until: "2019-12-31" + update: + - href: "https://localhost:8904/api/cardholders/325/access_groups/10ad21" + until: "" + remove: + - href: "https://localhost:8904/api/cardholders/325/access_groups/10ed27" + + competencies: + type: object + description: | + 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](#definition-Cardholder-competency). + + 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. + + example: + add: + - competency: + href: "https://localhost:8904/api/competencies/2354" + enabled: false + enablement: "2021-01-01T08:00+13" + update: + - href: "https://localhost:8904/api/cardholders/325/competencies/2dc3" + enabled: true + + relationships: + type: object + description: | + 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](#definition-Cardholder-relationship). + + 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. + + example: + add: + - role: + href: "https://localhost:8904/api/roles/5396" + cardholder: + href: "https://localhost:8904/api/cardholders/5398" + update: + - href: "https://localhost:8904/api/cardholders/325/roles/1799lah1170" + cardholder: + href: "https://localhost:8904/api/cardholders/10135" + + lockers: + type: object + description: | + With you well in the habit by now, each element of your three arrays should be in the + [cardholder locker schema](#definition-Cardholder-locker). 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. + + example: + add: + - locker: + href: "https://localhost:8904/api/lockers/1200" + from: "2019-01-01" + update: + - href: "https://localhost:8904/api/cardholders/325/lockers/wxyz1234" + until: "2020-02-29" + remove: + - href: "https://localhost:8904/api/cardholders/325/lockers/abcd4321" + + operatorGroups: + type: object + description: | + This can contain two arrays named 'add' and 'remove'. Every element you send in those + arrays should be in the [cardholder operator group + schema](#definition-Cardholder-operator-group). + + 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. + + example: + add: + - operatorGroup: + href: "https://localhost:8904/api/operator_groups/532" + - operatorGroup: + href: "https://localhost:8904/api/operator_groups/535" + remove: + - href: "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD" + + elevatorGroups: + type: object + description: | + This can contain three arrays named 'add', 'update', and + 'remove', each containing an element in the [cardholder elevator group + schema](#definition-Cardholder-elevator-group). + + 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: + add: + - elevatorGroup: + href: "https://localhost:8904/api/elevator_groups/635" + accessZone: + href: "https://localhost:8904/api/access_zones/637" + update: + - href: "https://localhost:8904/api/cardholders/325/elevator_groups/1268613268" + enableCodeBlueFeatures: true + enableVipFeatures: false + remove: + - href: "https://localhost:8904/api/cardholders/325/elevator_groups/3498734" + + Cardholder PDF: + description: | + 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](#definition-Cardholder-detail), and you can send it + in a [POST](#definition-Cardholder-POST-example) or a + [PATCH](#definition-Cardholder-PATCH-example) 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 '@'. + + properties: + href: + type: string + format: url + readonly: true + description: | + 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. If the PDF is not an image the content type will be text/plain, encoded (like + everything else in this API) as UTF-8. + + It is read-only: do not send it in a PATCH or POST. + + definition: + type: object + readonly: true + description: | + The definition object is read-only: do not send it in a PATCH or POST. + properties: + href: + type: string + format: url + description: | + This is the href of the PDF's definition, common to all cardholders who hold a value + for this PDF. + name: + type: string + description: | + The PDF's name, without an '@' prefix. + id: + type: string + description: | + Short alphanumeric identifier used elsewhere in the API to filter cardholder searches + and add PDF values to alarm and event GETs. + type: + type: string + enum: [ string, image, strEnum, numeric, date, address, phone, email, mobile ] + + value: + type: string + description: | + In a GET response for text and numeric PDFs this will be a scalar containing their actual + value, but for image PDFs it will be an object containing a copy of the `href` field + above. + + 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. + + notifications: + type: boolean + description: | + 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: + '@cellphone': + href: "https://localhost:8904/api/cardholders/325/personal_data/9998" + definition: {href: "https://localhost:8904/api/personal_data_fields/9998", + name : "cellphone", id: "9998", type: "mobile"} + value: "a@b.com" + notifications: false + + Cardholder card: + description: | + 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](#definition-Cardholder-detail), 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 [document of their own](piv.html). + + properties: + href: + description: | + 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. + + type: string + format: url + example: "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9" + number: + description: | + 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. + + type: string + required: false + example: "1" + cardSerialNumber: + description: | + The serial number (CSN) of a physical access card as a hex string without a leading '0x'. + + The example in this example shows the byte ordering you can expect in a GET and should use + in a PATCH or POST. The card serials (UIDs) of NXP MIFARE cards start with 04 and end + with 80. + + This may not be not present on older cards. + + This field was read-only until 7.90. It is read-write in 8.00 and later. + + type: string + example: "045A5769713E80" + required: false + issueLevel: + description: | + 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. + + type: integer + minimum: 0 + maximum: 15 + example: 1 + required: false + status: + description: | + 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. + + type: object + properties: + value: + description: | + 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 have extra values covered [in the PIV + supplement](piv.html#definition-PIV-GET-example). + + 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. + + 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 ('active', + for the factory state set). + + type: string + example: "Disabled (manually)" + type: + description: | + 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. + + type: string + enum: [ "pending", "active", "expired", "inactive"] + readOnly: true + example: "inactive" + + type: + description: | + 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. + + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/card_types/354" + name: {type: string, example: "Card type no. 1"} + + invitation: + description: | + 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 dependence on cellular connectivity is + undesirable. 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](https://gallaghersecurity.github.io). + + properties: + email: + type: string + example: "nick@example.com" + description: | + The email address to which Command Centre will send or did send an invitation for this + credential. + mobile: + type: string + example: "02123456789" + description: | + The telephone number to which Command Centre will send or did send an SMS containing a + confirmation code for this invitation. + singleFactorOnly: + type: boolean + example: true + default: false + description: | + 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. + + status: + type: string + example: "sent" + enum: [notSent, sent, expired, accepted] + readOnly: true + description: | + + - `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. + + href: + type: string + format: url + example: "https://security.gallagher.cloud/api/invitations/abcd1234defg5678" + readOnly: true + description: | + 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'. + from: + description: | + 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. + type: string + format: date-time + example: "2017-01-01T00:00:00Z" + until: + description: | + 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. + type: string + format: date-time + example: "2017-12-31T11:59:59Z" + + credentialClass: + readOnly: true + description: | + 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. + type: string + enum: [ 'card', 'digitalId', 'govPass', 'mobile', 'piv', 'pivi', 'trackingTag', 'transact' ] + example: mobile + trace: + description: | + 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. + type: boolean + example: false + lastPrintedOrEncodedTime: + type: string + format: date-time + readOnly: true + description: | + 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. + example: "2020-08-10T09:20:50Z" + lastPrintedOrEncodedIssueLevel: + type: integer + minimum: 1 + maximum: 15 + example: 1 + readOnly: true + description: | + 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. + pin: + type: string + example: "153624" + description: | + Even though this appears in the example of a cardholder GET, 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 when the server converts them to + integers. Putting quotes around the PIN forces the server to treat them as strings, which + preserves any leading zeros. + + 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. + visitorContractor: + description: | + 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. + + type: boolean + example: false + ownedBySite: + description: | + 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. + + readOnly: true + type: boolean + example: false + credentialId: + description: Reserved for use by Gallagher applications. + example: "reserved" + bleFacilityId: + description: Reserved for use by Gallagher applications. + example: "reserved" + + Cardholder card mobile example: + description: | + This is an example of what you would send to create a new mobile credential. The [card + detail](#definition-Cardholder-card) contains everything in a card, and is a bit daunting, so + this example shows only the fields you need for a mobile credential. + + You can submit it as part of a larger document in a [POST](#definition-Cardholder-detail) to + create a cardholder with cards, or as part of a [PATCH](#definition-Cardholder-PATCH-example) to + create a mobile credential on an existing cardholder. + + properties: + number: + example: "Nick's mobile" + type: string + description: "The so-called card 'number' on a mobile credential does not need to be numeric." + status: + description: | + Optional. Provide a field `value` set to one of the valid starting states for the mobile + credential. If you omit it, the credential will start in the default state. + example: {value: "active"} + type: + description: | + This is the only compulsory field in the POST body. It must contain an href to the card + type of the new mobile credential. + example: {"href": "https://localhost:8904/api/card_types/654"} + from: {type: string, format: date-time, example: "2017-01-01T00:00:00Z"} + until: {type: string, format: date-time, example: "2018-01-01T00:00:00Z"} + invitation: + description: | + This 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-Cardholder-card) for more. + properties: + email: {type: string, example: "nick@example.com"} + mobile: {type: string, example: "02123456789"} + singleFactorOnly: {type: boolean, example: true} + + Cardholder card physical example: + description: | + This is a minimal object for creating a card, containing the only required field: the card + type. See the [card detail](#definition-Cardholder-card) for other fields you can use, such + as the card's issue level and from/until dates. + + You can submit it as part of a larger document in a [POST](#definition-Cardholder-detail) to + create a cardholder with cards, or as part of a [PATCH](#definition-Cardholder-PATCH-example) to + assign a card to an existing cardholder. + + properties: + type: + description: This is the only compulsory field in the POST body. + It must contain an href to the card type of the new card. + example: {href: "https://localhost:8904/api/card_types/600" } + + Cardholder access group: + description: | + An _access group_ is an object in Command Centre. The connection between an access group and + a cardholder is 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. + + properties: + href: + type: string (url) + description: | + 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 will always return a 404. + + example: "https://localhost:8904/api/cardholders/325/access_groups/D714D8A89F" + accessGroup: + description: | + An object containing a link to this group's detail page and its name. It is read-only + because you cannot change a group membership's group. + + 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). + readOnly: true + example: { name: "R&D special projects group", href: "https://localhost:8904/api/access_groups/352" } + status: + description: | + The two fields in this block are read-only because they are determined by the `from` and + `until` dates. + readOnly: true + type: object + properties: + value: + description: | + 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. + type: string + example: "Pending" + readOnly: true + type: + description: | + 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'. + + type: string + readOnly: true + enum: [ "pending", "active", "expired"] + example: "pending" + from: + description: | + 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. In 7.90.883 or earlier, + this should always be in UTC with a trailing 'Z'. + type: string + format: date-time + example: "2017-01-01T00:00:00Z" + until: + description: | + 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. In 7.90.883 or earlier, + this should always be in UTC with a trailing 'Z'. + type: string + format: date-time + example: "2017-12-31T11:59:59Z" + + Cardholder operator group: + description: | + 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. + + properties: + href: + type: string (url) + description: | + DELETE this URL or use it in the 'operatorGroups' block of a [cardholder + PATCH](#operation--api-cardholders--id--patch) to remove this operator group membership. + + DELETE is the only verb you can use on this URL. GET will always return a 404. + + example: "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD" + operatorGroup: + description: | + An object containing a link to this operator group's detail page and its name. It is + marked read-only because you do not send it to the server when managing a cardholder's + operator groups: you cannot change a group membership's group. + + The link will be absent if your operator does not have the privilege to view the operator + group ('View operators' or 'Edit operators', for example). + readOnly: true + example: { name: "Locker admins", href: "https://localhost:8904/api/operator_groups/532" } + + Cardholder competency: + description: | + 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. + + The REST API allows you to link 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 is a block in the cardholder detail. You can also put it in the results of a + cardholder search by putting `competencies` in the `fields` parameter. + + This section describes what you receive from a GET. The [Cardholder competency + update](#definition-Cardholder-competency-update) describes what you should send in a POST or + PATCH to set or update a cardholder's link to a competency. + + properties: + href: + readOnly: true + type: string + format: url + description: | + Use this URL 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 always return a 404. + + example: "https://localhost:8904/api/cardholders/325/competencies/2dc3p0" + competency: + readOnly: true + description: | + This contains the competency's name and its href. They are read only because they + belong to the competency itself, not the cardholder's link to it. + type: object + example: { + href: "https://localhost:8904/api/competencies/2354", + name: "Hazardous goods handling"} + status: + readOnly: true + description: | + 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 competency. Whether it is _expired_ is derived from an expiry date (actually + a timestamp accurate to the second): if it is in the past, Command Centre considers the + competency expired. + + A competency can also have an 'enable' date. If that date (timestamp) passes while the + competency is disabled, Command Centre will enable it. It will leave the enable 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 'expires' time is important. If it is in the past, + 'type' will be 'expired'. + + All of those cases are negative. Two remain, when our cardholder is blessed with an + enabled and active competency. 'Type' will be 'expiryDue' if the 'expires' time is in the + future but within the competency's advance notice period, or 'active' if the 'expires' + time is beyond the advance notice period or not set at all. + + You can see and set the enabled flag, the enable date, and the expiry date via this + API. + + When creating a cardholder or updating a competency on an existing cardholder, you should + set the 'enabled' field one way or the other, and the 'enablement' and 'expiry' dates if + you wish. They will determine the contents of this status block. + + Here it is in table form. The first two rows are the positive cases, when Command Centre + would grant access to a competency-enforced zone. + + | Enabled flag | Enablement date | Expiry date | `status.type` + | ------------ | -------------- | --------| ----- | + | true | - | Far future | active | + | true | - | Near future | expiryDue | + | true | - | Past | expired | + | false | Future | - | pending | + | false | Past | - | inactive | + | false | null | - | inactive | + + type: object + example: + value: "Pending" + type: "pending" + + expiryWarning: + type: string + format: date-time + readOnly: true + description: | + 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'. + example: "2017-03-06T15:45:00Z" + expiry: + type: string + format: date-time + description: >- + 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'. + example: "2017-03-09T15:45:00Z" + enablement: + type: string + format: date-time + description: >- + 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'. + example: "2018-03-09T15:45:00Z" + comment: + type: string + description: "The comment appears in the management clients when viewing the cardholder." + example: "CPR refresher due March." + limitedCredit: + type: boolean + description: | + 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. + + example: true + credit: + type: integer + description: | + 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: 37 + + Cardholder competency update: + description: | + This is a description of the fields you would supply when updating a cardholder's holding of a + competency. It is slightly different from what you receive in a cardholder GET: you do not + send read-only blocks such the status and the competency, and there is a write-only field + 'enabled' that you can supply to change the status. + + You cannot change the competency to which the status, times, comment, and credit applies. 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 raise a stateful alarm depending on your version + of Command Centre. + + required: [ href ] + properties: + href: + type: string + required: true + format: url + description: "Required. This identifies the cardholder competency you are updating." + example: "https://localhost:8904/api/cardholders/325/competencies/2dc3p0" + enabled: + type: boolean + writeOnly: true + description: | + 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 to disable the + competency now, particularly if you are setting a future enablement date. + example: true + expiry: + type: string + format: date-time + description: >- + The time at which you want to expire the competency. See the full description + [above](#definition-Cardholder-competency). + example: "2017-03-09T15:45:00Z" + enablement: + type: string + format: date-time + description: >- + The time at which you want to re-enable the competency. You can set this on an enabled + competency but it has no effect there, so with this field you would generally also set + enabled to false. See the full description [above](#definition-Cardholder-competency). + example: "2018-03-09T15:45:00Z" + comment: + type: string + example: "CPR refresher due March." + limitedCredit: + type: boolean + description: | + If you set this false, Command Centre's 'Pre-pay Car Parking' feature (available under its + own licence) will not reduce the current credit. + example: true + credit: + type: integer + description: | + The amount of credit left on this competency for use by Pre-pay Car Parking. It can be + negative. + example: 37 + + Cardholder relationship: + description: | + 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. + + properties: + href: + type: string + format: url + description: | + 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. + + example: "https://localhost:8904/api/cardholders/325/relationships/179lah1170" + role: + type: object + description: | + 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. + example: + href: "https://localhost:8904/api/roles/5396" + name: "Supervisor" + cardholder: + type: object + description: | + 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://localhost:8904/api/cardholders/5398" + name: "Miles Messervy" + firstName: "Miles" + lastName: "Messervy" + + Cardholder locker: + description: | + 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. + + properties: + href: + type: string + format: url + description: | + 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](#operation--api-cardholders--id--patch). + + DELETE is the only verb you can use on this URL. GET will always return a 404. + + example: "https://localhost:8904/api/cardholders/325/lockers/t1m4" + locker: + description: | + 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. + + type: object + example: + name: "Bank A locker 1" + shortName: "A1" + lockerBank: + href: "https://localhost:8904/api/locker_banks/4567" + name: "Bank A" + href: "https://localhost:8904/api/lockers/3456" + from: + description: | + The start of the time period during which the cardholder has access to this locker. + + Send an empty string `""` to reset it. + type: string + format: date-time + example: "2017-01-01T00:00:00Z" + until: + description: | + 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. + type: string + format: date-time + example: "2018-12-31T00:00:00Z" + + Cardholder elevator group: + description: | + These appear in an array in a cardholder detail, showing the 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. + + properties: + href: + type: string + format: url + description: | + The href of this cardholder's elevator group entry. DELETE this to remove it from the + cardholder. + example: "https://localhost:8904/api/cardholders/325/elevator_groups/567" + elevatorGroup: + description: | + The href and name of the elevator group for which this cardholder has a default + floor or passenger types. + example: { + href: "https://localhost:8904/api/elevator_groups/635", + name: "Main building lower floors" } + accessZone: + description: | + 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. + example: { + href: "https://localhost:8904/api/access_zones/637", + name: "Lvl 1 lift lobby"} + enableCaptureFeatures: + type: boolean + description: | + 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. + example: true + enableCodeBlueFeatures: + type: boolean + description: | + 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. + example: false + enableExpressFeatures: + type: boolean + description: | + 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. + example: true + enableServiceFeatures: + type: boolean + description: | + 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. + example: false + enableService2Features: + type: boolean + description: | + 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. + example: true + enableService3Features: + type: boolean + description: | + 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. + example: true + enableVipFeatures: + type: boolean + description: | + 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: false + +###################################################################### + + Cardholder changes: + description: | + An array of cardholder changes, described in the next section, and a `next` link for more. + properties: + results: + type: array + description: An array of cardholder changes. + items: { $ref: '#/definitions/Cardholder change' } + + next: + type: object + description: | + 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. + + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/cardholders/changes?pos=SWEp9" + + Cardholder change: + description: | + `/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`. + + properties: + href: + type: string + format: url + description: | + 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. + example: "https://localhost:8904/api/cardholders/changes/f4e67a" + time: + type: string + format: date-time + description: The time that this change occurred. + example: "2020-01-14T03:14:33Z" + type: + description: | + 'add' if this change added a cardholder, 'update' if it modified a cardholder, or 'remove' + if it deleted a cardholder. + type: string + enum: [ add, update, remove ] + example: "update" + item: + description: | + 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). + type: object + example: {"href":"https://localhost:8904/api/cardholders/525"} + operator: + description: | + 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`. + type: object + example: {"name": "System Operator", "href": "https://localhost:8904/api/items/1"} + oldValues: + description: | + 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](#definition-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. + + example: + firstName: "Craig" + competencies: + - "enablement": "" + "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4" + newValues: + description: | + 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`. + + example: { + firstName: "Gavin", + competencies: [ + { + "enablement": "2020-02-29T00:00:00Z", + "href": "https://localhost:8904/api/cardholders/525/competencies/3910e4" + } + ], + cards: [ + { + "number": "2", + "cardSerialNumber": "", + "issueLevel": 1, + "from": "", + "until": "", + "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba" + } + ], + "accessGroups": [ + { + "accessGroup": { + "name": "Access Group 1", + "href": "https://localhost:8904/api/access_groups/499" + }, + "from": "", + "until": "2020-01-15T04:39:00Z", + "href": "https://localhost:8904/api/cardholders/525/access_groups/f9cb328b4" + } + ] } + cardholder: + description: | + 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](#definition-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: { + firstName: "Gavin", + cards: [ + { + "number": "2", + "cardSerialNumber": "", + "issueLevel": 1, + "from": "", + "until": "", + "href": "https://localhost:8904/api/cardholders/525/cards/285f779af1ef49abbba" + } + ] } + +###################################################################### + + Cardholder redaction: + description: | + 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. + + properties: + href: + type: string + format: url + description: | + DELETE this URL to cancel this redaction. + + DELETE is the only verb you can use on this URL. GET will always return a 404. + + example: "https://localhost:8904/api/cardholders/redactions/625" + type: + type: string + enum: [ normalEvents, cardholder ] + description: | + Whether this redaction is for cardholder events or cardholder information. + example: "normalEvents" + when: + description: | + 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. + + type: string + format: date-time + example: "2023-01-01T00:00:00Z" + before: + description: | + For event redactions, do not redact any events after this time. No effect on cardholder + information redactions. + + Optional. + + type: string + format: date-time + example: "2022-01-01T00:00:00Z" + status: + type: string + enum: [ pending, inProgress, cancelled, done, failed ] + description: | + The status of this redaction. + example: "pending" + redactionOperator: + description: | + A block containing the href and current name of the operator who scheduled the redaction. + + type: object + example: {"name": "REST Operator", "href": "https://localhost:8904/api/items/100"} + +###################################################################### + + Access group search: + description: | + An array of access group summaries, described in the next section, and a `next` link for more. + properties: + results: + type: array + description: An array of access group summaries. + items: + { $ref: '#/definitions/Access group summary' } + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: + type: string + format: url + example: + href: "https://localhost:8904/api/access_groups?skip=61320" + + Access group summary: + description: | + The access group search at `/api/access_groups` returns an array of these, and + `/api/access_groups/{id}` (linked as the href in this object) returns one with more fields. + + properties: + id: + type: string + description: | + An alphanumeric identifier for this access group. No API calls use access group IDs. + example: "352" + href: + type: string + format: url + description: | + A link to an [access group detail](#definition-Access-group-detail) object for this access + group. + example: "https://localhost:8904/api/access_groups/352" + name: + type: string + example: "R&D special projects group." + description: + type: string + example: "Deep underground." + parent: + type: object + description: "A link to the group's parent, and its name." + example: + href: "https://localhost:8904/api/access_groups/100" + name: "All R&D" + division: + type: object + description: "The division that contains this access group." + example: + id: "2" + href: "https://localhost:8904/api/divisions/2" + cardholders: + type: object + description: | + Following this link lists the group's [direct + memberships](#operation--api-access_groups--id--cardholders-get). + + 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. + example: + href: "https://localhost:8904/api/access_groups/352/cardholders" + serverDisplayName: + <<: *SERVER + + Access group detail: + description: | + [/api/access_groups/{id}](#operation--api-access_groups--id--get) 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'. + + allOf: + - type: object + properties: + children: + type: array + description: | + 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. + + items: + type: object + properties: + href: + description: "The href of the child access group." + type: string + format: url + name: + description: "The name of the child access group." + type: string + example: + - href: "https://localhost:8904/api/access_groups/5122" + name: "R&D super-special projects" + - href: "https://localhost:8904/api/access_groups/3420" + name: "R&D social committee" + + personalDataDefinitions: + type: object[] + description: | + The names and hrefs of the PDFs that this access group gives to its members. + example: [ { + "name": "email", + "href": "https://localhost:8904/api/personal_data_fields/5516" + }, { + "name": "cellphone", + "href": "https://localhost:8904/api/personal_data_fields/9998" + } ] + + visitor: + description: | + 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. + + example: false + type: boolean + + escortVisitors: + description: | + 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. + + example: false + type: boolean + + lockUnlockAccessZones: + description: | + 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. + + example: false + type: boolean + + enterDuringLockdown: + description: | + If true, members of this group are not subject to lockdown restrictions when + requesting to enter its access zones. + + New to 8.40. + + example: false + type: boolean + + firstCardUnlock: + description: | + 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. + + example: false + type: boolean + + overrideAperioPrivacy: + description: | + 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. + + example: false + type: boolean + + aperioOfflineAccess: + description: | + 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. + + example: false + type: boolean + + disarmAlarmZones: + description: | + 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. + + example: false + type: boolean + + armAlarmZones: + description: | + 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. + + example: false + type: boolean + + hvLfFenceZones: + description: | + 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. + + example: false + type: boolean + + viewAlarms: + description: | + If true, members of this group can view alarms and inputs on remote arming + terminals ("RATs") and HBUS terminals. + + New to 8.40. + + example: false + type: boolean + + shunt: + description: | + If true, members of this group can shunt (isolate) items using RATs and + HBUS terminals. + + New to 8.40. + + example: false + type: boolean + + lockOutFenceZones: + description: | + 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. + + example: false + type: boolean + + cancelFenceZoneLockout: + description: | + 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. + + example: false + type: boolean + + ackAll: + description: | + 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. + + example: false + type: boolean + + ackBelowHigh: + description: | + 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. + + example: false + type: boolean + + selectAlarmZone: + description: | + 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. + + example: false + type: boolean + + armWhileAlarm: + description: | + 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. + + example: false + type: boolean + + armWhileActiveAlarm: + description: | + 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. + + example: false + type: boolean + + isolateAlarmZones: + description: | + 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. + + example: false + type: boolean + + access: + type: object[] + description: | + Names and hrefs of the access zones to which this access group gives access, and the + schedules that govern it. + + 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. + + example: [ { + "accessZone": { + "href": "https://localhost:8904/api/access_zones/333", + "name": "Twilight zone" + }, + "schedule": { + "href": "https://localhost:8904/api/schedules/5", + "name": "Default Cardholder Access Granted" + } + }, { + "accessZone": { + "href": "https://localhost:8904/api/access_zones/412", + "name": "Server room" + }, + "schedule": { + "href": "https://localhost:8904/api/schedules/557", + "name": "8am-5pm weekdays" + } } ] + + saltoAccess: + type: array + description: | + 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. + + items: + description: | + Each element in the array contains three blocks: the Salto item type, the item + itself, and the controlling schedule. + type: object + properties: + saltoItemType: + description: | + This block tells you whether the Salto item is a Salto door or a Salto 'access + zone'. + type: object + properties: + value: + type: string + enum: ['saltoAccessZone', 'saltoDoor' ] + saltoItem: + description: | + The name and href of the Salto zone or Salto door to which this access group + gives access. + + example: [ { + "saltoItemType": { + "value": "saltoAccessZone" + }, + "saltoItem": { + "href": "https://localhost:8904/api/items/570", + "name": "Salto BLE CV19" + }, + "schedule": { + "href": "https://localhost:8904/api/schedules/5", + "name": "Default Cardholder Access Granted" + } + }, { + "saltoItemType": { + "value": "saltoDoor" + }, + "saltoItem": { + "href": "https://localhost:8904/api/items/579", + "name": "Salto CU5000" + }, + "schedule": { + "href": "https://localhost:8904/api/schedules/557", + "name": "8am-5pm weekdays" + } } ] + + alarmZones: + type: object[] + description: | + Names and hrefs of the alarm zones to which members of this access group have the + 20-odd management privileges listed above. + + Added in 8.40. + + example: [ { + "alarmZone": { + "href": "https://localhost:8904/api/alarm_zones/328", + "name": "Roswell building 2 lobby alarms" + } }, { + "alarmZone": { + "href": "https://localhost:8904/api/alarm_zones/10138", + "name": "Roswell building 3 lobby alarms" + } } ] + + - $ref: "#/definitions/Access group summary" + + Access group membership: + description: | + Returned in an array by + [/api/access_groups/{id}/cardholders](#operation--api-access_groups--id--cardholders-get), + 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](#operation--api-cardholders--id--patch). + + Each also contains an href at the top level: DELETE that to remove the membership. + + properties: + href: + type: string + format: url + description: | + 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. + + cardholder: {type: object, description: 'The name and href of the member cardholder.'} + from: {type: string, format: time-stamp} + until: {type: string, format: time-stamp} + example: + href: "https://localhost:8904/api/cardholders/325/access_groups/D714D8A894724F" + cardholder: + name: "Boothroyd, Algernon" + href: "https://localhost:8904/api/cardholders/325" + from: "2017-01-01T00:00:00Z" + until: "2017-12-31T11:59:59Z" + +###################################################################### + + Locker bank search: + description: | + An array of locker bank summaries, and a `next` link for more. + + properties: + results: + type: array + description: An array of locker summaries. + items: { $ref: '#/definitions/Locker bank summary' } + example: + - 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: + type: object + description: The link to the next page of results. Absent if you have retrieved them all. + properties: + href: { type: string, format: url, example: "https://localhost:8904/api/locker_banks?skip=2" } + + Locker bank summary: + description: | + 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. + + The most important field is the href to the detail page, covered next. + + properties: + href: + type: string + format: url + description: "A link to the detail page for this locker bank." + name: + type: string + shortName: + type: string + description: "The bank's short name." + description: + type: string + division: + type: object + description: 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" } + + Locker bank detail: + description: | + [/api/locker_banks/{id}](#operation--api-locker_banks--id--get) 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. + + properties: + href: + type: string + format: url + description: A self-reference. + name: + type: string + shortName: + type: string + description: + type: string + division: + type: object + description: The division containing this locker bank. + + notes: + type: string + connectedController: + <<: *CONNECTEDCONTROLLER + lockers: + type: array + items: + type: object + properties: + name: { type: string } + shortName: + type: string + description: "A T20 will display this instead of the long name, if it has both." + description: { type: string } + href: + type: string + format: url + description: | + 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. + connectedController: + <<: *CONNECTEDCONTROLLER_BRIEF + assignments: + type: array + description: | + 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. + items: + type: object + properties: + href: + type: string + format: url + description: | + This is the href of a cardholder's assignment to a locker. Put it in a + [cardholder PATCH](#operation--api-cardholders--id--patch) to update or delete + it. + + You can also revoke a cardholder's access to this locker by sending a + [DELETE](#operation--api-cardholders--id--lockers--assignment_id--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. + cardholder: + type: object + description: | + 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. + from: + type: string + format: date-time + description: | + If missing, the locker assignment has no start time, meaning the cardholder + can open the locker provided the 'until' time is in the future. + until: + type: string + format: date-time + description: | + If missing, the locker assignment has no end time, meaning the cardholder can + open the locker provided the 'from' time is in the past. + description: | + An array of locker objects, each containing the details of the locker and the + cardholders who can open it. + + 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" + - 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" + + Locker detail: + description: | + [/api/lockers/{id}](#operation--api-locker--id--get) 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. + + properties: + href: + type: string + format: url + description: A self-reference. + example: "https://localhost:8904/api/lockers/3456" + name: + type: string + example: "Lobby locker 1" + shortName: + type: string + example: "L1" + description: + type: string + example: "Wheelchair-suitable" + division: + type: object + description: The division containing this locker. + example: { id: "2", href: "https://localhost:8904/api/divisions/2" } + notes: + type: string + description: The notes field is not in the default result set. You must ask for it using `fields`. + connectedController: + <<: *CONNECTEDCONTROLLER + assignments: + type: array + description: | + 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. + items: + type: object + properties: + href: + type: string + format: url + description: | + This is the href of a cardholder's assignment to a locker. Put it in a [cardholder + PATCH](#operation--api-cardholders--id--patch) to update or delete it. + + You can also revoke a cardholder's access to this locker by sending a + [DELETE](#operation--api-cardholders--id--lockers--assignment_id--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. + cardholder: + type: object + description: | + 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. + from: + type: string + format: date-time + description: | + If missing, the locker assignment has no start time, meaning the cardholder can open + the locker while the 'until' time is in the future. + until: + type: string + format: date-time + description: | + If missing, the locker assignment has no end time, meaning the cardholder can open + the locker after the 'from' time. + example: + - 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: + type: object + description: | + Overrideable items return one of these blocks if your operator has the right privilege + ('Override - Open Locker' in this case) and the item is fit to be overridden. + + The only override you can send to a locker is 'open', so that is the only entry this block + will contain. + properties: + open: + type: object + properties: + href: + type: string + format: url + description: | + [POST](#operation--api-lockers--id--open-post) to this to override the locker to + open its door. + example: "https://localhost:8904/api/lockers/3456/open" + + Competency PATCH and POST example: + type: object + description: | + 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. + + properties: + name: + type: string + description: | + 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 value for you. + + example: "New competency" + shortName: + type: string + maxLength: 16 + description: "If you supply a string that is too long, Command Centre will truncate it." + example: "C4" + description: + type: string + description: The new item's description. + example: "Translated automatically." + division: + type: object + description: The division to contain this competency. + example: {href: "https://localhost:8904/api/divisions/2" } + notes: + type: string + description: | + A string, able to me much longer than `description`, suitable for holding notes about the + item. + example: "A very long string." + + Competency search: + description: An array of competency summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of competency summaries. + items: { $ref: '#/definitions/Competency summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/competencies?skip=1000" + + Competency summary: + description: | + 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. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/competencies/2354" + description: |- + This is the identifier to use when assigning a competency to a cardholder in a cardholder + [PATCH](#operation--api-cardholders--id--patch) or + [POST](#definition-Cardholder-POST-example). + + id: {type: string, example: "2354"} + name: {type: string, example: "Hazardous goods handling"} + description: {type: string, example: "Required for access to chem sheds."} + serverDisplayName: {type: string, example: "ruatoria.satellite.net"} + notes: {type: string, example: ""} + + Competency detail: + description: | + [/api/competencies/{id}](#operation--api-competencies--id--get) returns one of these. It + contains the same fields you see when you view or edit a competency in the Configuration + client. + properties: + href: {type: string, format: url, example: "https://localhost:8904/api/competencies/2354"} + id: {type: string, example: "2354"} + name: {type: string, example: "Hazardous goods handling"} + description: {type: string, example: "Required for access to chem sheds."} + serverDisplayName: {type: string, example: "ruatoria.satellite.net"} + division: + type: object + description: The division containing this competency. + example: {id: "2", href: "https://localhost:8904/api/divisions/2" } + notes: {type: string, example: ""} + shortName: {type: string, example: ""} + expiryNotify: + type: boolean + example: false + description: | + 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. + noticePeriod: + description: | + 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. + properties: + units: + type: string + enum: [ "days", "weeks", "months", "years" ] + example: "weeks" + number: {type: integer, minimum: 0, maximum: 999, example: 2, default: 0 } + defaultExpiry: + type: object + description: | + 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. + properties: + expiryType: + type: string + enum: [ "none", "durationdays", "durationweeks", "durationmonths", "durationyears", "date" ] + example: "durationmonths" + default: none + description: If 'none', there is no default expiry. + + expiryValue: + description: | + 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. + type: string or number + example: 6 + defaultAccess: + type: string + example: "fullAccess" + enum: [ "noAccess", "readOnly", "fullAccess" ] + default: "fullAccess" + description: | + 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. + + Card type search: + description: | + 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. + properties: + results: + type: array + description: An array of card types. + items: { $ref: '#/definitions/Card type' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/card_types/assign?skip=1000" + + Card type: + description: | + This object describes a single Card Type. "Credential type" would be a better name, as it + includes mobile credentials. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/card_types/600" + description: |- + This is the identifier to use when assigning a card to a cardholder in a cardholder + [PATCH](#operation--api-cardholders--id--patch) or + [POST](#definition-Cardholder-POST-example). + + This is also the URL for the card type's detail page. That contains nothing of use, so + there is no point GETting it. Doing so will return you a 404 if you do not have 'View + site' or 'Configure site' on the card type's division. + + id: {type: string, example: "600"} + name: {type: string, example: "Red DESFire visitor badge"} + division: + type: object + description: The division that contains this card type. New to 8.50. + example: + id: "2" + href: "https://localhost:8904/api/divisions/2" + notes: + type: string + example: "Disabled after 7d inactivity, 6-char PIN" + description: | + 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`. + facilityCode: + type: string + description: | + 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. + example: "A12345" + availableCardStates: + type: array + items: {type: string, enum: ['Active', 'Disabled (manually)', 'Lost', 'Stolen', 'Damaged']} + description: | + All credential types have a set of card states. + + If you need this, ask for it using the `fields` parameter. + example: + - "Active" + - "Disabled (manually)" + - "Lost" + - "Stolen" + - "Damaged" + credentialClass: + type: string + enum: [ "piv", "pivi", "card", "mobile", "digitalId", "govPass", "trackingTag", "transact" ] + example: card + minimumNumber: + description: | + For card types with integer card numbers, this is the minimum. + type: string + example: "1" + maximumNumber: + description: | + For card types with integer card numbers, this is the maximum. + type: string + example: "16777215" + serverDisplayName: + <<: *SERVER + regex: + description: | + This is the regular expression that a Card Type text card number + must match before Command Centre will accept it. + type: string + example: "^[A-Za-z0-9]+$" + regexDescription: + description: Regular expressions often need explaining. + type: string + example: "Only alphanumeric characters" + defaultExpiry: + description: Reserved for internal use. + example: "Reserved" + +###################################################################### + + Operator group search: + description: | + An array of operator group summaries, described in the next section, and a `next` link for + more. + properties: + results: + type: array + description: An array of operator group summaries. + items: + { $ref: '#/definitions/Operator group summary' } + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: + type: string + format: url + example: + href: "https://localhost:8904/api/operator_groups?skip=61320" + + Operator group summary: + description: | + 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. + + properties: + href: + type: string + format: url + description: | + A link to an [operator group detail](#definition-Operator-group-detail) object for this + operator group. + example: "https://localhost:8904/api/operator_groups/523" + name: + type: string + example: "Locker admins." + serverDisplayName: + <<: *SERVER + + Operator group detail: + description: | + [/api/operator_groups/{id}](#operation--api-operator_groups--id--get) returns one of these. + In addition to the basic items details such as division and description, it lists the + divisions in which it grants privileges to its members. + + allOf: + - type: object + properties: + description: + type: string + example: "For managing locker assignments." + division: + type: object + description: | + The division that contains this operator group. This has no bearing on the divisions + in which this operator group grants privileges; that is `divisions`. + example: + id: "2" + href: "https://localhost:8904/api/divisions/2" + cardholders: + type: object + description: | + Following this link lists the group's [cardholder + members](#operation--api-operator_groups--id--cardholders-get). + example: + href: "https://localhost:8904/api/operator_groups/523/cardholders" + divisions: + type: array + description: | + An array containing the divisions in which this operator group grants its privileges. + example: [ + division: { + "name": "Staff", + "href": "https://localhost:8904/api/divisions/647" + }, + division: { + "name": "Contractors", + "href": "https://localhost:8904/api/divisions/649" + } + ] + + - $ref: "#/definitions/Operator group summary" + + Operator group membership: + description: | + Returned in an array by + [/api/operator_groups/{id}/cardholders](#operation--api-operator_groups--id--cardholders-get), + 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](#operation--api-cardholders--id--patch) the href in the cardholder block if you want + to change anything about that cardholder. The operator groups they are in, for example. + + properties: + href: + type: string + format: url + description: | + [DELETE](#operation--api-cardholders--id--operator_groups--membership_id--delete) this URL + to remove the cardholder from this operator group. + + DELETE is the only verb you can use on this URL. GET will always 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. + + cardholder: {type: object, description: '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" + +###################################################################### + PDF definition search: + description: | + An array of PDF definition summaries, and a `next` link for more. + + properties: + results: + type: array + description: An array of PDF definition summaries. + items: { $ref: '#/definitions/PDF definition' } + example: + - name: "email" + id: "5516" + href: "https://localhost:8904/api/personal_data_fields/5516" + - name: "cellphone" + id: "9998" + href: "https://localhost:8904/api/personal_data_fields/9998" + serverDisplayName: "ruatoria.satellite.int" + next: + type: object + description: The link to the next page of results. Absent if you have retrieved them all. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/personal_data_fields?pos=900&sort=id" + + PDF definition: + description: | + `/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. + properties: + id: + type: string + description: | + 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. + name: + type: string + example: "email" + serverDisplayName: + <<: *SERVER + description: + type: string + example: "Corporate mailbox" + division: + type: object + description: The division containing this PDF definition. + example: {id: "2", href: "https://localhost:8904/api/divisions/2" } + type: + type: string + description: "The type of PDF: string, image, email address, etc." + enum: [string, image, strEnum, numeric, date, address, phone, email, mobile] + example: "email" + default: + description: This field will be missing if there is no default value. + type: string + example: "contact@example.com" + required: + type: boolean + example: false + description: | + If true, every cardholder with this PDF must have a value for it. No blanks allowed. + unique: + type: boolean + example: false + description: | + 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. + defaultAccess: + type: string + example: "fullAccess" + enum: [ "noAccess", "readOnly", "fullAccess" ] + default: "fullAccess" + description: | + 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. + operatorAccess: + description: | + 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. + type: string + enum: [ "noAccess", "readOnly", "fullAccess" ] + example: "fullAccess" + sortPriority: + description: | + 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. + type: integer + example: 50 + accessGroups: + description: | + 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. + type: array + example: + - 'name': "All Staff" + - 'name': "R&D Special Projects Group" + 'href': "https://localhost:8904/api/access_groups/352" + regex: + description: | + This is the regular expression that a PDF value must match before Command Centre will + accept it. + type: string + example: ".*@.*" + regexDescription: + description: Regular expressions often need explaining. + type: string + example: "@ least" + notificationDefault: + description: | + 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. + + New in 8.50. + type: boolean + example: false + imageWidth: + description: | + 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. + + New in 8.50. + type: integer + example: 600 + imageHeight: + description: | + 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. + + New in 8.50. + type: integer + example: 800 + imageFormat: + description: | + 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. + type: string + enum: [bmp, jpg, png] + example: "jpg" + contentType: + description: | + 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.70. + type: string + enum: [image/bmp, image/jpeg, image/png] + example: "image/jpeg" + isProfileImage: + description: | + True if and only if this PDF holds images and it is set as a profile image. + + New in 8.70. + type: boolean + example: false + +###################################################################### + + Reception search: + description: | + An array of receptions, and a `next` link for more. + + properties: + results: + type: array + description: An array of receptions. + items: { $ref: '#/definitions/Reception' } + example: + - name: "Main lobby" + href: "https://localhost:8904/api/receptions/937" + - name: "Green Dragon main desk" + href: "https://localhost:8904/api/receptions/979" + next: + type: object + description: The link to the next page of results. Absent if you have retrieved them all. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/receptions?pos=1000" + + Reception: + description: | + `/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. + properties: + name: + type: string + example: "Main lobby" + href: + type: string + format: url + description: | + This is the href to use when creating a visit. + example: "https://localhost:8904/api/receptions/937" + serverDisplayName: + <<: *SERVER + description: + type: string + example: "Security foyer in B1" + description: | + Short free-form text. Searches do not return an item's description - ask for it using the + `fields` parameter. + division: + type: object + description: The division containing this reception. + example: {id: "2", href: "https://localhost:8904/api/divisions/2" } + defaultVisitorType: + type: object + description: | + 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. + example: + href: "https://localhost:8904/api/divisions/2/v_t/925" + accessGroup: + name: "Visitor group 1" + href: "https://localhost:8904/api/access_groups/925" + notes: + type: string + description: | + Free-form text. You will only get this field if you ask for it with the `fields` + parameter. + +###################################################################### + Redaction: + description: | + An array of these comes from `GET /cardholders/redactions`. + + allOf: + - type: object + properties: + cardholder: + type: object + description: | + The href of the cardholder whose events or item this redaction should affect. + + Required. + example: {"href": "https://localhost:8904/api/cardholders/630"} + finished: + type: string + description: | + When the redaction finished. + + Will be missing from pending redactions. + format: date-time + example: "2022-01-01T00:00:00Z" + message: + type: string + description: | + Translated string from the redaction's error code. Will be absent if empty, or if the + redaction is pending or complete. + example: "Invalid cardholder" + details: + type: string + description: | + A more detailed description of what went wrong. + + Not translated. Will be absent if empty, or if the redaction is pending or complete. + + example: "" + - $ref: "#/definitions/Cardholder redaction" + +###################################################################### + Redaction POST Example: + description: | + POST one of these to schedule a redaction. + + required: [ cardholder, type ] + properties: + cardholder: + type: object + description: | + The href of the cardholder whose events or item this redaction should affect. + + Required. + example: {"href": "https://localhost:8904/api/cardholders/630"} + type: + type: string + enum: [ normalEvents, cardholder ] + description: | + Whether this redaction is for events or cardholder information. + + Required. + example: "normalEvents" + when: + type: string + description: | + 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. + + format: date-time + example: "2023-01-01T00:00:00Z" + before: + type: string + description: | + For event redactions, do not redact any events after this time. No effect on cardholder + information redactions. + + Optional. + + format: date-time + example: "2022-01-01T00:00:00Z" + +###################################################################### + + Role search: + description: | + An array of roles, and a `next` link for more. + + properties: + results: + type: array + description: An array of roles. + items: { $ref: '#/definitions/Role' } + example: + - name: "Supervisor" + id: "1383" + href: "https://localhost:8904/api/roles/1383" + - name: "Contract manager" + id: "1399" + href: "https://localhost:8904/api/roles/1399" + serverDisplayName: "ruatoria.satellite.int" + next: + type: object + description: The link to the next page of results. Absent if you have retrieved them all. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/roles?pos=1000&sort=id" + + Role: + description: | + `/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. + properties: + name: + type: string + example: "Supervisor" + href: + type: string + format: url + description: | + This is the string to use when creating a relationship between cardholders using this + role. + example: "https://localhost:8904/api/roles/1399" + serverDisplayName: + <<: *SERVER + description: + type: string + example: "aka floor manager" + division: + type: object + description: The division containing this role. + example: {id: "2", href: "https://localhost:8904/api/divisions/2" } + id: + type: string + description: | + An alphanumeric identifier, unique to the server. No API calls use role IDs. + example: "1383" + +###################################################################### + + Visit search: + description: | + An array of visits, and a `next` link for more. + + properties: + results: + type: array + description: An array of visits. + items: { $ref: '#/definitions/Visit' } + example: + - name: "Supervisor" + id: "1383" + href: "https://localhost:8904/api/visits/1383" + - name: "Contract manager" + id: "1399" + href: "https://localhost:8904/api/visits/1399" + serverDisplayName: "ruatoria.satellite.int" + next: + type: object + description: The link to the next page of results. Absent if you have retrieved them all. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/visits?pos=1000&sort=id" + + Visit: + description: | + `GET /api/visits` returns an array of these, `GET /api/visits/{id}` returns one, and you can + send one in a POST or PATCH to `/api/visits`. + + properties: + name: + <<: *VISIT_NAME + href: + type: string + format: url + description: | + This is the URL to send a PATCH to when modifying an existing visit. It comes out of a + GET or in the Location header of a POST that creates a visit, and the server will ignore + it if you send it in the body of a POST or a PATCH. + example: "https://localhost:8904/api/visits/941" + serverDisplayName: + <<: *SERVER + description: + <<: *VISIT_DESCRIPTION + division: + type: object + description: | + The division containing this visit. This 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. + + You cannot change a visit's division directly. You can affect it indirectly by changing + the visit's reception. + example: {id: "2", href: "https://localhost:8904/api/divisions/2" } + reception: + <<: *VISIT_RECEPTION + visitorType: + <<: *VISIT_VISITORTYPE + host: + <<: *VISIT_HOST + from: + <<: *VISIT_FROM_UNTIL + example: "1971-03-08T14:35:00Z" + until: + <<: *VISIT_FROM_UNTIL + example: "2021-03-08T14:35:00Z" + location: + <<: *VISIT_LOCATION + visitorAccessGroups: + <<: *VISIT_AGS + visitors: + <<: *VISIT_ORS + + Visit POST example: + description: | + 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. + + required: [ name, reception, visitorType, host, from, until ] + properties: + name: + <<: *VISIT_NAME + description: + <<: *VISIT_DESCRIPTION + reception: + <<: *VISIT_RECEPTION + example: + href: "https://localhost:8904/api/receptions/937" + visitorType: + <<: *VISIT_VISITORTYPE + example: + href: "https://localhost:8904/api/access_groups/925" + host: + <<: *VISIT_HOST + example: + href: "https://localhost:8904/api/cardholders/526" + from: + <<: *VISIT_FROM_UNTIL + until: + <<: *VISIT_FROM_UNTIL + example: "2023-03-08T14:35:00Z" + location: + <<: *VISIT_LOCATION + visitorAccessGroups: + <<: *VISIT_AGS + example: + - href: "https://localhost:8904/api/access_groups/926" + - href: "https://localhost:8904/api/access_groups/9260" + visitors: + <<: *VISIT_ORS + example: + - href: https://localhost:8904/api/cardholders/940 + - href: https://localhost:8904/api/cardholders/9040 + + Visit PATCH example: + description: | + 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 is that all fields are optional in a PATCH. + + properties: + name: + <<: *VISIT_NAME + description: + <<: *VISIT_DESCRIPTION + reception: + <<: *VISIT_RECEPTION + example: + href: "https://localhost:8904/api/receptions/937" + visitorType: + <<: *VISIT_VISITORTYPE + example: + accessgroup: + href: "https://localhost:8904/api/access_groups/925" + host: + <<: *VISIT_HOST + example: + href: "https://localhost:8904/api/cardholders/526" + from: + <<: *VISIT_FROM_UNTIL + until: + <<: *VISIT_FROM_UNTIL + example: "2023-03-08T14:35:00Z" + location: + <<: *VISIT_LOCATION + visitorAccessGroups: + <<: *VISIT_AGS + example: + 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: + <<: *VISIT_ORS + example: + 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/937 + + Visitor PATCH example: + description: | + This is a sample PATCH body that marks a visitor as signing in. + + properties: + status: + type: object + description: | + This is the same status that you see in a [visit GET](#definition-Visit) for each visitor + except that you only need to send the `value` field. + properties: + value: + type: string + description: | + An enum describing the state in which you want your visitor to be. + example: "signingIn" + enum: + [expected, signingIn, signedIn, onSite, expectedBack, departed, cancelled ] + +###################################################################### +###################################################################### +###################################################################### +paths: + +###################################################################### + + /api/cardholders: + get: + tags: + - Cardholders + summary: Search cardholders + description: | + 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. + + Take this URL from the 'href' field in the `features.cardholders.cardholders` section of + `/api`. + + parameters: + - $ref: "#/parameters/sort" + - name: "top" + in: query + required: false + type: integer + minimum: 1 + description: | + 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. + + - name: "name" + in: query + required: false + type: string + + description: | + 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. + + - name: "pdf_{id}" + in: query + required: false + type: string + description: | + Limits the results to cardholders with a value for the Personal Data Field with this ID + that matches the parameter. + + By default, it is a substring match; 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, adding a percent sign `%` or underscore `_` will anchor the string + at both ends. A `_` will match any single character, and a `%` will match any substring. + A lone `%` will return any cardholder with this PDF set to a non-null value. + + 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. + + The 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'. + + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + + - name: accessZone + in: query + required: false + type: array + items: { type: string } + description: | + 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, + or if an operator manually moves them outside. + + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + + enum: [] + + description: | + 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](#operation--api-cardholders--id--get). + 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 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. + + 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. + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Cardholder search'} + 400: + description: The server could not make sense of your search terms. + 403: + description: The site does not have the RESTCardholders licence. + + post: + tags: + - Cardholders + + summary: Create a cardholder + + description: | + Creates a new cardholder, including his or her cards, group memberships, personal data, + competencies, and roles. + + Take this URL 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](#definition-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](#definition-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. + + parameters: + - name: "not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Cardholder POST example'} + description: | + This can be a large object as shown, or a tiny one, because the only fields you must + have in the POST are the division and either the first or last name. + responses: + 201: + description: "Success." + headers: + location: + type: string + format: url + description: The href of the new cardholder. + 400: + description: | + 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. + + 403: + description: | + 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. + + /api/cardholders/{id}: + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the cardholder. + get: + tags: + - Cardholders + summary: Get details of a cardholder + description: | + Full details for a cardholder. Follow the href in the [cardholder + search](#definition-Cardholders) to get here. + parameters: + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + + enum: + + description: | + Specifies the fields you want in the results. The values you can list are the same as the + field names in the [detail results](#definition-Cardholder-detail). 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. + + responses: + 200: + description: "Success." + schema: { $ref: '#/definitions/Cardholder detail' } + 404: + description: | + That is not the URL of a cardholder, or the operator does not have the privilege to view + that cardholder. + + patch: + tags: [ Cardholders ] + summary: Update a cardholder + description: | + 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. + + --- + + 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 Cardholder to disable cards. + + - Modify Access Control. This privilege does not work on its own. You need + Edit Cardholder as well as Modify Access Control to change group memberships. + + - Add or Edit Cardholder Notes. You also need Edit Cardholder to change notes + on an existing cardholder via the API. In the administrative clients, you do + not. + + - Manage Locker Assignments. You also need Edit Cardholder 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 Cardholder privilege. + + In short, if your application intends to do more to cardholders than de-authorise them, you + will need an operator with Edit Cardholder. + + --- + + The PATCH is best illustrated by example. + + parameters: + - name: ignored + in: body + required: true + schema: {$ref: '#/definitions/Cardholder PATCH example'} + description: | + As well as cardholder attributes such as 'authorised', the PATCH body contains + instructions for creating, updating, and deleting personal data, group + memberships, etc. + + responses: + 200: + description: | + Success. The response body will contain feedback from the server about your PATCH. + 204: + description: "Success." + 400: + description: | + 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. + 403: + description: | + The site does not have a RESTCardholders licence, or you attempted to set a PDF for + which you have no privilege. + 409: + <<: *409CH + 4xx: + description: | + 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). 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. + + delete: + tags: [ Cardholders ] + summary: Remove a cardholder + description: | + This call removes a cardholder from Command Centre. + responses: + 204: { description: "Success." } + 400: + description: | + Deleting the cardholder failed. This happens when the cardholder is a critical part of + another construct (a personalised notification, for example). + 403: + description: | + There is no such cardholder, or the operator does not have permissions to delete it, + or the server is not licensed for cardholder operations. + 409: + <<: *409CH + + /api/cardholders/{id}/access_groups/{membership_id}: + delete: + tags: + - Cardholders + summary: Remove an access group membership + + description: | + 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](#definition-Cardholder-detail). + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the cardholder." + - name: "membership_id" + in: path + required: true + type: string + description: "The identifier of the cardholder's membership in the access group." + responses: + 204: + description: "Success." + 403: + description: | + The operator does not have a privilege that allows editing that cardholder. + 404: { description: "That is not the href of an access group membership." } + 409: + <<: *409CH + 4xx: + description: | + The operator does not have a privilege that allows editing that cardholder's access + group memberships (such as 'Modify access control'). + + /api/cardholders/{id}/cards/{card_id}: + delete: + tags: + - Cardholders + + summary: Remove a card from a cardholder + description: | + This call removes a card from a cardholder. + + You can find this URL in the [cardholder object](#definition-Cardholder-detail). + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the cardholder." + - name: "card_id" + in: path + required: true + type: string + description: "An opaque identifier for the card." + responses: + 204: { description: "Success." } + 403: { description: "The operator does not have a privilege that allows editing that cardholder." } + 404: { description: "That card is not on that cardholder." } + 409: + <<: *409CH + + /api/cardholders/{id}/competencies/{link_id}: + delete: + tags: + - Cardholders + + summary: Remove a competency from a cardholder + description: | + This call removes a competency from a cardholder. + + You can find this URL in the [cardholder object](#definition-Cardholder-detail). + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the cardholder." + - name: "link_id" + in: path + required: true + type: string + description: "An opaque identifier for the link between the cardholder and the competency." + responses: + 204: { description: "Success." } + 403: { description: "The operator does not have a privilege that allows editing that cardholder." } + 404: { description: "That is not the href of a cardholder's competency." } + + /api/cardholders/{id}/competencies/{link_id}/credit: + post: + tags: + - Cardholders + + summary: Change competency credit + description: | + 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: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the cardholder." + - name: "link_id" + in: path + required: true + type: string + description: "An opaque identifier for the link between the cardholder and the competency." + - name: "credit" + in: path + required: true + type: integer + description: "The amount to adjust the competency credit. This can be positive or negative." + responses: + 204: { description: "Success." } + 403: { description: "The operator does not have a privilege that allows editing that cardholder." } + 404: { description: "The parameters are invalid." } + + /api/cardholders/{id}/elevator_groups/{assignment_id}: + delete: + tags: + - Cardholders + + summary: Remove an elevator group from a cardholder + description: | + 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](#definition-Cardholder-detail). + + responses: + 204: + description: Success. + 403: + description: | + Your operator does not have the necessary privilege to change this cardholder's elevator + groups. + 404: + description: | + 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. + + /api/cardholders/{id}/lockers/{assignment_id}: + delete: + tags: + - Cardholders + + summary: Remove a locker assignment + description: | + 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](#definition-Cardholder-detail). + + responses: + 204: + description: Success. + 404: + description: | + 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. + 4xx: { description: "You do not have privileges for the operation." } + + /api/cardholders/{id}/operator_groups/{membership_id}: + delete: + tags: + - Cardholders + summary: Remove an operator group membership + + description: | + 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](#definition-Cardholder-detail). + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the cardholder." + - name: "membership_id" + in: path + required: true + type: string + description: "The identifier of the cardholder's membership in the operator group." + responses: + 204: + description: "Success." + 403: + description: | + The operator does not have a privilege that allows editing that cardholder's operator + group memberships ('Modify operator group membership'). + 404: { description: "That is not the href of an operator group membership." } + 409: + <<: *409CH + + /api/cardholders/{id}/roles/{relationship_id}: + delete: + tags: + - Cardholders + + summary: Remove a relationship + description: | + This call severs a relationship between two cardholders. + + You can find the URL in the `relationships` block in the [cardholder + object](#definition-Cardholder-detail) 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. + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the cardholder." + - name: "relationship_id" + in: path + required: true + type: string + description: "An opaque identifier for the link between the cardholder and the role." + responses: + 204: + description: "Success." + 403: { description: "The operator does not have a privilege that allows editing that cardholder." } + 404: { description: "That is not the URL of a relationship. Perhaps it is deleted already." } + + /api/cardholders/{id}/update_location: + post: + tags: + - Cardholders + + summary: Change a cardholder's location + description: | + This call updates a cardholder's location (moves them) to a target access zone. Added in 8.20. + + Take this URL from the 'updateLocation.href' field in a [cardholder + response](#definition-Cardholder-detail). + + 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](rest.html#operation--api-access_zones-update_cardholder_location-get) 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](events.html#operation--api-items-get). + + Note that to change a cardholder's location 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. + + 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. + + parameters: + - name: "" + in: body + required: true + schema: {$ref: '#/definitions/Cardholder Update Location POST example'} + example: {"accessZone":{"href": "https://localhost:8904/items/412"}} + description: | + This should contain the href of the target access zone. The example here is a different + style from the linked example, which contains `access_zones` in the URL rather than + `items`, because it came from the items controller. Both styles work. + responses: + 204: + description: "Success." + 400: + description: | + The cardholder ID or access zone href is invalid. + 403: + description: | + The operator does not have a privilege ('Manage Cardholder Location') that allows moving + this cardholder to the target zone. + + /api/cardholders/{id}/edit: + get: + tags: + - Cardholders + summary: Find out which fields you can edit + + description: | + 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. + + responses: + 200: + description: Success. + 4xx: { description: "The parameter is invalid or you do not have privileges for the operation." } + +###################################################################### + + /api/cardholders/changes: + get: + tags: + - Cardholder changes + summary: Get changes + description: | + 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, don't 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. + + Take this URL 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: + - name: "top" + in: query + required: false + type: integer + minimum: 1 + maximum: 1000 + description: Sets the maximum number of changes to return per page. + + - name: "pos" + in: query + required: false + type: integer + minimum: 0 + default: null + description: Reserved for internal use. + + - name: "filter" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, firstName, lastName, shortName, description, authorised, lastSuccessfulAccessZone, division, notes, useExtendedAccessTime, personalDataFields, operatorLoginEnabled, operatorUsername, operatorPassword, operatorPasswordExpired, windowsLoginEnabled, windowsUsername, cards, accessGroups, competencies, notifications, relationships, lockers] + + description: | + 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](#definition-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. + + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: + [href, operator, operator.href, operator.name, time, type, item, oldValues, newValues, cardholder, cardholder.* ] + + description: | + 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](#field-names-in-query-parameters). 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` to monitor changes to PDF values on a cardholder. + + - name: "deadline" + in: query + required: false + type: integer + minimum: 0 + default: 50 + description: | + 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. + + Added in 8.80. + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Cardholder changes'} + 4xx: + description: | + The site does not have the RESTCardholders licence. + 500: + description: | + The server was too busy to complete the request before the deadline. + +###################################################################### + + /api/access_groups: + get: + tags: + - Access groups + summary: Search access groups + + description: | + + 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`. + + parameters: + - $ref: "#/parameters/sort" + - name: "top" + in: query + required: false + type: integer + minimum: 1 + description: | + 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. + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, description, parent, division, children, notes, personalDataDefinitions, cardholders, access, saltoAccess, alarmZones, ...] + description: | + 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](#definition-Access-group-detail). 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. + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: { $ref: '#/definitions/Access group search' } + 403: { description: "The site does not have the RESTCardholders licence." } + + /api/access_groups/{id}: + get: + tags: + - Access groups + summary: Get details of an access group + + description: | + 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](#operation--api-access_groups-get). + + You can find the URL for this call in the access group search results and in a cardholder's + `accessGroups` array. + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the access group." + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, description, parent, division, children, notes, personalDataDefinitions, access, saltoAccess, alarmZones, ...] + description: | + Sets the list of fields to return. The values you can list are the same as the field + names in the [detail results](#definition-Access-group-detail). 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. + + responses: + 200: + description: "Success." + schema: { $ref: '#/definitions/Access group detail' } + 404: + description: | + 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'. + + /api/access_groups/{id}/cardholders: + get: + tags: + - Access groups + summary: Get membership of an access group + + description: |- + + 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. + + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the access group." + responses: + 200: + description: "Success." + schema: { type: array, items: {$ref: '#/definitions/Access group membership' }} + example: + cardholders: + - href: "https://localhost:8904/api/cardholders/325/access_groups/D714D8A894724F" + cardholder: + name: "Boothroyd, Algernon" + href: "https://localhost:8904/api/cardholders/325" + from: "2017-01-01T00:00:00Z" + until: "2017-12-31T11:59:59Z" + - href: "https://localhost:8904/api/cardholders/329/access_groups/18DE901A1DEA48" + cardholder: + name: "Miles Messervy" + from: "2016-11-18T00:00:00Z" + + 404: + description: | + 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. + +###################################################################### + + /api/competencies: + post: + tags: + - Competencies + summary: Create a competency [coming soon] + description: | + Creates a new competency. + + The POST expects a document in the same format as the [the competency + detail](#definition-Competency) but with far fewer fields. An example is [this POST + example](#definition-Competency-PATCH-and-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`. + + Creating a competency is coming in a future version of Command Centre. + + parameters: + - name: "this name not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Competency PATCH and POST example'} + description: | + There are no mandatory fields. If you do not specify a division when creating a + competency, or any other item for that matter, the API will put it in the root division. + + responses: + 201: + description: | + Success. Check the response body for feedback about your request. + headers: + location: + type: string + format: url + description: The href of the new competency. + 400: + description: | + 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. + + 403: + description: | + 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. + + get: + tags: + - Competencies + summary: Search competencies + description: |- + 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`. + + parameters: + - $ref: "#/parameters/sort" + - $ref: "#/parameters/top" + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, shortName, description, division, notes, expiryNotify, noticePeriod, defaultExpiry, defaultAccess] + description: | + 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](#definition-Competency-detail). 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. + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Competency search'} + 403: { description: "The installation lacks a cardholders licence." } + + /api/competencies/{id}: + get: + tags: + - Competencies + summary: Get details of a competency + description: |- + You get this URL from a cardholder or from a competency search. + + 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: + - name: "id" + in: path + required: true + type: string + description: "The alphanumeric identifier of the competency you are after." + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, shortName, description, division, notes, expiryNotify, noticePeriod, defaultExpiry, defaultAccess] + description: | + Specifies which fields to return. The values you can list are the same as the field names + in the [details page](#definition-Competency-detail). Use it to reduce the size of the + result document. Separate values with commas. + + Treat the string matches as case sensitive. + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Competency detail'} + 403: { description: "The installation lacks a cardholders licence." } + 404: + description: | + Your REST operator does not have the privilege to view this competency. + + patch: + tags: + - Competencies + summary: Update a competency [coming soon] + description: | + This is the call you use to update a division's name, short name, description, notes, or + division. + + The PATCH expects a document in the same format as the [the competency + detail](#definition-Competency-detail) but with fewer fields. An example is [this PATCH + example](#definition-Competency-PATCH-and-POST-example). + + Patching competencies is coming to a future version of Command Centre. + + parameters: + - name: "this name not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Competency PATCH and POST example'} + description: | + There are no mandatory fields. + + responses: + 200: + description: | + Success. The response body will contain feedback from the server about your PATCH. + 204: + description: "Success." + 400: + description: | + 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. + 403: + description: | + 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 either the 'Configure Site' 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. + + 404: + description: | + 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](#operation--api-competencies--operation--get). + + 409: + description: | + The item is locked for editing by another operator. The body of the response will + tell you which operator is holding the lock. + + delete: + tags: [ Competencies ] + summary: Remove a competency [coming soon] + description: | + This call removes a competency from Command Centre. + + Deleting competencies is coming to a future version of Command Centre. + responses: + 204: { description: "Success." } + 400: + description: | + Deleting the competency failed. This happens when the item is still being used by + another item. + 403: + description: | + The operator has the permission to view the item but not delete it, or the server is not + licensed for the operation. + 404: + description: | + That is not the URL of a competency, or the operator is not privileged to view it. + +###################################################################### + /api/card_types: + get: + tags: [ "Card types" ] + summary: Search card types + description: | + 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](#operation--api-card_types-assign-get) instead. + + /api/card_types/assign: + get: + tags: [ "Card types" ] + summary: Search usable card types + description: | + 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. Perhaps there are none in the divisions in which your operator has 'View site', + 'Configure site', or the other privileges that allow assign cards and credentials to + cardholders. + + You can find this URL in `features.cardTypes.assign`in `/api`. It was new in 8.10. + + parameters: + - $ref: "#/parameters/sort" + - $ref: "#/parameters/top" + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - name: "fields" + in: query + type: array + items: { type: string } + enum: [ href, id, name, division, facilityCode, availableCardStates, credentialClass, minimumNumber, maximumNumber, notes, regex, regexDescription ] + + description: | + 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'. + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Card type search'} + + 403: { description: "The installation lacks a cardholders licence." } + + /api/card_types/{id}: + get: + tags: [ "Card types" ] + summary: Get one card type + description: | + 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](#operation--api-card_types-assign-get) + instead. + +###################################################################### + /api/locker_banks: + get: + tags: [ "Lockers" ] + summary: Search locker banks + description: | + 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. + + Take this URL from the 'href' field in the `features.lockerBanks.lockerBanks` section of + `/api`. + + parameters: + - $ref: "#/parameters/sort" + - name: "top" + in: query + required: false + type: integer + minimum: 1 + description: | + Sets the maximum number of locker banks to return per page. The default depends on your + server version; you should set it appropriately for your application. + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, shortName, description, division, lockers, notes, lockers.defaults, lockers.id, lockers.href, lockers.name, lockers.shortName, lockers.description, lockers.division, lockers.notes, lockers.commands, lockers.assignments] + + description: | + Sets which fields to return. The values you can list are the same as the + field names in the [details page](#definition-Locker-bank-detail). 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. + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Locker bank search'} + 403: + description: | + The installation lacks a lockers licence, or it is missing RESTCardholders and + RESTStatus and (in 8.60) RESTOverrides. + + /api/locker_banks/{id}: + get: + tags: [ "Lockers" ] + summary: Get details of a locker bank + description: | + This returns details for a locker bank. Follow the href in the [locker bank + summary](#definition-Locker-bank-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: An internal identifier of the locker bank. + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, shortName, description, division, notes, connectedController, lockers, lockers.defaults, lockers.connectedController, ...] + description: | + Sets which fields to return. The values you can list are the same as the field names you + would get in the [details page](#definition-Locker-bank-detail). 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. + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Locker bank detail'} + 403: + description: | + The installation lacks a lockers licence, or it is missing RESTCardholders and + RESTStatus and (in 8.60) RESTOverrides. + 404: + description: | + Your REST operator does not have the privilege to view that locker bank. + + /api/lockers/{id}: + get: + tags: [ "Lockers" ] + summary: Get details of a locker + description: | + This returns details for a locker. Follow the href in the [locker bank + summary](#definition-Locker-bank-summary) or [locker bank + detail](#definition-Locker-bank-detail) to get here. + + Prior to v8.20 this endpoint was at `/api/locker_banks/{locker_bank_id}/lockers/{id}` . + + parameters: + - name: "id" + in: path + required: true + type: string + description: An internal identifier of the locker bank. + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, shortName, description, division, assignments, notes, commands, connectedController] + description: | + 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. + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Locker detail'} + 403: + description: | + The installation lacks a lockers licence, or it is missing both RESTCardholders and + RESTStatus and (in 8.60) RESTOverrides. + 404: + description: | + Your REST operator does not have the privilege to view that locker bank ('Locker + assignments'). + + /api/lockers/{id}/open: + post: + tags: [ "Lockers" ] + summary: Open a locker + description: | + Sends an override to open a locker. + + You should get this href from a [locker detail](#definition-Locker-detail). + + New in 8.10.1112. + parameters: + - {in: path, name: id, required: true, description: "The ID of the locker to open.", type: string} + responses: + 204: { description: Success. } + 403: + description: | + 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. + 404: + description: | + 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. + +###################################################################### + + /api/operator_groups: + get: + tags: + - Operator groups + summary: Search operator groups + + description: | + + 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`. + + Added in 8.50. + + parameters: + - $ref: "#/parameters/sort" + - name: "top" + in: query + required: false + type: integer + minimum: 1 + description: | + Sets the maximum number of operator groups to return per page. The default depends on your + server version; you should set it appropriately for your application. + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [href, id, name, description, division, notes, cardholders, divisions] + description: | + Sets which fields to return. The values you can list are the same as the + field names in the [details page](#definition-Operator-group-detail). 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. + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: { $ref: '#/definitions/Operator group search' } + 403: { description: "The site does not have the RESTCardholders licence." } + + /api/operator_groups/{id}: + get: + tags: + - Operator groups + summary: Get details of an operator group + + description: | + 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](#operation--api-operator_groups-get). + + You can find the URL for this call in the operator group search results and in a + cardholder's `operatorGroups` array. + + Added in 8.50. + parameters: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the operator group." + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + enum: [defaults, href, id, name, description, division, notes, cardholders, divisions] + description: | + Sets which fields to return. The values you can list are the same as the field names in + the [detail results](#definition-Operator-group-detail). Use it to return the notes, + which don't appear by default. Separate values with commas. + + Treat the string matches as case sensitive. + + responses: + 200: + description: "Success." + schema: { $ref: '#/definitions/Operator group detail' } + 403: { description: "The site does not have the RESTCardholders licence." } + 404: + description: | + 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'). + + /api/operator_groups/{id}/cardholders: + get: + tags: + - Operator groups + summary: Get membership of an operator group + + description: |- + + 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. + + 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: + - name: "id" + in: path + required: true + type: string + description: "The identifier of the operator group." + - name: "fields" + in: query + required: false + type: array + items: { type: string } + default: ['defaults'] + + enum: [ defaults, cardholder, href ] + + description: | + 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. + + responses: + 200: + description: "Success." + schema: { type: array, items: {$ref: '#/definitions/Operator group membership' }} + example: + cardholders: + - href: "https://localhost:8904/api/cardholders/325/operator_groups/EBDRSD" + cardholder: + name: "Boothroyd, Algernon" + href: "https://localhost:8904/api/cardholders/325" + - href: "https://localhost:8904/api/cardholders/329/operator_groups/AFADEMED" + cardholder: + name: "Miles Messervy" + + 4xx: + description: | + The ID is invalid, or it is valid but you do not have privileges for the operator group. + Check the body of the result for a description of the problem. + +###################################################################### + /api/personal_data_fields: + get: + tags: [ "PDF definitions" ] + summary: Search PDF definitions + description: | + 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. + + Take this URL from the href in the `features.personalDataFields.personalDataFields` + section of `/api`. + + parameters: + - $ref: "#/parameters/sort" + - $ref: "#/parameters/top" + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - $ref: "#/parameters/pdf_fields" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/PDF definition search'} + 403: + description: The site does not have the RESTCardholders or RESTEvents licence. + + /api/personal_data_fields/{id}: + get: + tags: [ "PDF definitions" ] + summary: Get details of a PDF definition + description: | + This returns details for a PDF definition. Follow the href in the + [summary](#definition-PDF-definition) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: An internal identifier for the PDF definition. + - $ref: "#/parameters/pdf_fields" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/PDF definition'} + 403: + description: | + The site does not have the RESTCardholders or RESTEvents licence + 404: + description: | + 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. + +###################################################################### + /api/receptions: + get: + tags: [ "Receptions" ] + summary: Search receptions + description: | + 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. + + Take this URL from the href in the `features.receptions.receptions` section of `/api`. + + parameters: + - $ref: "#/parameters/sort" + - $ref: "#/parameters/top" + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - $ref: "#/parameters/reception_fields" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Reception search'} + 403: + description: The site does not have the RESTCardholders licence. + + /api/receptions/{id}: + get: + tags: [ "Receptions" ] + summary: Get details of a reception + description: | + This returns details for a reception. Follow the href in the + [summary](#definition-Reception-search) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: An internal identifier for the reception. + - $ref: "#/parameters/reception_fields" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Reception'} + 403: + description: | + The site does not have the RESTCardholders or RESTEvents licence + 404: + description: | + 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'). + +###################################################################### + /api/cardholders/redactions: + get: + tags: [ "Redactions" ] + summary: Search redactions + description: | + All the cardholders' redactions. Paginated. + + parameters: + - name: "cardholder" + in: query + required: false + type: string + description: | + Limits the results to redactions to cardholders with these IDs. Separate with commas. + + - name: "status" + in: query + required: false + type: string + enum: [ pending, inProgress, cancelled, done, failed ] + description: | + Limits the results to redactions with this status. Separate with commas. + + - name: "type" + in: query + required: false + type: string + enum: [ normalEvents, cardholder ] + description: | + Limits the results to redactions of this type. Separate with commas. + + - name: "after" + in: query + required: false + type: string + format: date-time + description: | + Limits the results to redactions that were scheduled to occur after this time. + + - name: "before" + in: query + required: false + type: string + format: date-time + description: | + Limits the results to redactions that were scheduled to occur before this time. + + - name: "fields" + in: query + required: false + type: array + items: { type: string } + + enum: [before cardholder finishTime href redactionOperator status type when ] + + description: | + Specifies the fields you want in the results. + + Treat the string matches as case sensitive: use `lastName` rather than `lastname`. + + responses: + 200: + description: An array of redactions, and a link to the next page if any. + schema: + type: object + properties: + results: + type: array + items: { $ref: '#/definitions/Redaction' } + next: + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/cardholders/redactions?top=1&pos=625" + 403: + description: The site does not have the RESTCardholders licence. + post: + tags: [ "Redactions" ] + summary: Schedule a redaction + description: | + Schedules a cardholder redaction. + + parameters: + - name: "this string not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Redaction POST Example'} + description: | + Required fields are `cardholder` and `type` + responses: + 201: + description: "Success." + 400: + description: | + The cardholder does not exist. + 403: + description: | + 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. + delete: + tags: [ "Redactions" ] + summary: Cancel a redaction + description: | + Deletes a redaction record. Has to be pending. + + 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 redaction's effect on auditing. + responses: + 201: + description: "Success." + 403: + description: | + The operator does not have permissions to redact or to delete a cardholder, + or the server is not licensed for cardholder operations. + 404: + description: | + No such redaction, or your operator cannot see it. + +###################################################################### + /api/roles: + get: + tags: [ "Roles" ] + summary: Search roles + description: | + 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. + + Take this URL from the href in the `features.roles.roles` section of `/api`. + + parameters: + - $ref: "#/parameters/sort" + - $ref: "#/parameters/top" + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Role search'} + 403: + description: The site does not have the RESTCardholders licence. + +###################################################################### + /api/visits: + get: + tags: [ "Visits" ] + summary: Search visits + description: | + 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. + + Take this URL from the href in the `features.visits.visits` section of `/api`. + + Visits are new to 8.50. + + parameters: + - $ref: "#/parameters/sort" + - $ref: "#/parameters/top" + - $ref: "#/parameters/name" + - $ref: "#/parameters/division" + - $ref: "#/parameters/description" + - $ref: "#/parameters/visit_fields" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Visit search'} + 403: + description: The site does not have both the RESTCardholders and VisitorManagement licences. + + post: + tags: [ "Visits" ] + summary: Create a visit + description: | + Creates a new visit. + + This POST expects a JSON body in the same format as a [visit detail](#definition-Visit). + + These fields are required: + - name + - reception + - visitor type (access group) + - host cardholder + - start time (`from`) + - end time (`until`) + + Take this URL from the href in the `features.visits.visits` section of `/api`. + + Like all the visit endpoints, this one is new to 8.50. + + parameters: + - name: "this string not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Visit POST example'} + description: | + 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. + responses: + 201: + description: "Success." + headers: + location: + type: string + format: url + description: The href of the new visit. + 400: + description: | + The body of the POST did not describe a valid visit. Check the body of the server's + response for hints. + 403: + description: The site does not have both the RESTCardholders and VisitorManagement licences. + + /api/visits/{id}: + get: + tags: [ "Visits" ] + summary: Get details of a visit + description: | + This returns details for a visit. Follow the href in the + [summary](#definition-Visit-search) to get here. + + All visit endpoints are new to 8.50. + + parameters: + - name: "id" + in: path + required: true + type: string + description: An internal identifier for the visit. + - $ref: "#/parameters/visit_fields" + + responses: + 200: + description: "Success." + schema: {$ref: '#/definitions/Visit'} + 403: + description: The site does not have both the RESTCardholders and VisitorManagement licences. + 404: + description: | + 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'). + + patch: + tags: [ "Visits" ] + summary: Modify a visit + description: | + Changes an existing visit. + + Follow the href in a [visit search](#definition-Visit-search) to get here. + + New to 8.50. + + parameters: + - name: "this string not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Visit PATCH example'} + description: | + 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. + responses: + 200: + description: | + Success. The response body will contain feedback from the server about your PATCH. + 204: + description: "Success." + 400: + description: | + 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. + 403: + description: | + 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. + 404: + description: | + 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'). + + /api/visits/{id}/visitors/{visitorId}: + patch: + tags: [ "Visits" ] + summary: Modify a visitor's status + description: | + 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. + + 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. + + Take this href from a [visit search](#definition-Visit-search) or [visit](#definition-Visit). + + New to 8.90. + + parameters: + - name: "this string not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Visitor PATCH example'} + description: | + This must contain a block called `status` containing a string called `value`, giving your + desired state of the visitor. + responses: + 204: + description: "Success." + 400: + description: | + 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. + + 403: + description: | + 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. + 404: + description: | + 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'). diff --git a/swagger/eventsApi.yaml b/swagger/eventsApi.yaml new file mode 100644 index 0000000..0f7c7b5 --- /dev/null +++ b/swagger/eventsApi.yaml @@ -0,0 +1,3582 @@ +## +## Copyright Gallagher Group Ltd 2020 All Rights Reserved +## THIS IS PROPRIETARY DOCUMENTATION SOURCE CODE OF +## Gallagher Group Research and Development +## Hamilton, New Zealand +## + +swagger: '2.0' +openapi: '3.0' + +info: + version: 1.0.0 + title: "Command Centre REST API: Alarms and Events" + description: | + This document describes how you can use the Command Centre REST API to download, monitor, and + create events, and monitor and manage alarms. + + It has companion documents describing the APIs for [Cardholders](cardholders.html), [PIV + cards](piv.html), and [non-cardholder items](rest.html). + + See the [events section](#tag-Events) for examples of how to create events, download events, + search for events using various criteria, and receive new events as they arrive. + + See the [alarms section](#tag-Alarms) for the methods you can use to download all + current alarms, receive notifications as they change state, and manage them. These + methods offer similar alarm-management functions to those in the Premier and Command + Centre clients. + + Helper methods allow you to look up Command Centre's internal identifiers for + cardholders, divisions, and other items, so that you can use those identifiers in + search filters. + + ### Licensing + + All of the GETs in this document and the POSTs that manage alarms require the RESTEvents + licence. The POST to create a new event requires RESTCreateEvents. + + 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. + + #### Alarms, events, and items API changes on the roadmap + + * You will be able to create and delete divisions, and modify some basic fields. + + #### Alarms and events API changes in 9.00 + + * A new event type `hasLocation` greatly simplifies the [monitoring of cardholder + movements](#operation--api-events-get), and + + * a new `location` block on events greatly simplifies interpreting them + ([introduction](#location-events-in-900), [use + case](#searching-for-events-that-indicate-location-or-movement), + [schema](#definition-Event-detail) ). + + #### 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 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 supported). + + * 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 here. + + #### Alarms and events 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 to find events that are associated with + any item. + + #### Alarms and events 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. + + #### Alarms and events API changes in 8.50 + + * The server property that turns off [client certificate checking](#authentication) 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. + + #### Alarms and events 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](#definition-Event-summary) 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](#operation--api-items-get) can now filter multiple item types. + + - Events now show their origin when they have arrived from a remote server (in a multiserver + environment). + + #### Alarms and events API changes in 8.30 + + - The [items API](#tag-Items) 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. + + #### Alarms and events 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. + + #### Alarms and events API changes in 8.10 + + - [Incoming events](#operation--api-events-post) + - Events now show their related [access groups and doors](#definition-Event-summary) + for external event types. + +schemes: + - https +produces: + - application/json +consumes: + - application/json + +tags: +- name: Alarms + description: | + + Use these methods to download, monitor, and manage Command Centre alarms. + + ### Alarm use cases + + #### Downloading and managing unprocessed alarms + 1. `GET /api` + 2. Follow the link at `features.alarms.alarms` [↪](#operation--api-alarms-get). 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, follow the link at + `updates.href`. It will block until there is a new alarm or a change to an + existing alarm. + +- name: Divisions + description: | + 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, and RESTOverrides. + +- name: Events + description: | + 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. 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. + + * 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` [↪](#operation--api-events-get). 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` + [↪](#operation--api-events-updates-get) 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` [↪](#operation--api-events-get) 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` [↪](#operation--api-events-get) 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` + [↪](#operation--api-events-updates-get) 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](#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` [↪](#operation--api-events-get) 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. + 5. 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](#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` [↪](#operation--api-events-get) 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](#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. + + #### Searching for events related to particular cardholders + 1. `GET /api` + + 2. Follow the link at `features.items.items.href` [↪](#operation--api-items-get), 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` [↪](cardholders.html#operation--api-cardholders-get) + 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. + + 1. Extract the item ID of your cardholder or cardholders from that page, repeating as + necessary for additional cardholders. + + 1. Follow the link on the `/api` page at `features.events.events.href` + [↪](#operation--api-events-get) 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,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 request all events, and the server will only send you + movements. + + If your server is running 9.00 or later, the 'View cardholder events' privilege is still an + excellent idea 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. Then look at the `type` field inside the `location` block: + + * If you are simply after a person's location, use the `afterLocation` field inside the + `location` block. That could be an access zone or division if they moved or attempted to + move, or a workstation or alarms terminal if they logged into one of those. The + `canonicalTypeName` field inside the block will tell you what kind of item it is. Future + versions of Command Centre or customisations might add more. + + * 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. It will be an access zone or (for departing + visitors) a reception. 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 kind of item it is. + + * 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 were. The difference is that with 'denied'-type + events, they started there too. + + Each of those fields is covered in the [schema definition](#definition-Event-detail). + + 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 (added in v8.00), you can use [that API](rest.html) to 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](#tag-Items) 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` [↪](#operation--api-events-post). + + There are some rules around creating events, so you should first have a careful read of the + [POST documentation](#operation--api-events-post). + + ### 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. + + ### 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. + +- name: Items + description: | + + 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](#operation--api-events-get) or a [status + subscription](#operation--api-items-updates-post) and need the ID of an item or an item type, or + when you are [creating an event](#operation--api-events-post) 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](rest.html) 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](#operation--api-items-get) 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](#operation--api-items-types-get). + + 1. [POST](#operation--api-items-updates-post) to create a subscription. Your program should + get the URL from `items.updates.href` in the results of `GET /api`. + + 1. Take the current state of your items from the results of that call. If that's all you + need, terrific. + + 1. But if you want to monitor their state, [GET](#operation--api-items-updates-get) the + `next.href` link that came in the results of the POST. 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. + + 1. Loop. + + ### Licensing + + Every REST licence enables the items controller: RESTEvents, RESTCreateEvents, RESTCardholders, + RESTStatus, and RESTOverrides. + +securityDefinitions: + "API key": + type: apiKey + name: Authorization + in: header + + description: | + Clients authenticate by including a pre-shared API key in the `Authorization` header of each + request. Command Centre generates an 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. + + The API key will be in the format `XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX` and should be in + the header following an authorisation method of `GGL-API-KEY` and a space. Both should be in + upper case. For example: + + `Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB` + + Early versions of Command Centre allowed you to omit the 'GGL-API-KEY'. Later versions of CC + and all versions of the API gateway will reject you with a 401 if you do that. + + 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. + + 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. + + 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. + +x-spectacle-topics: + Documentation suite: + $ref: "indexApi.yaml#/x-spectacle-topics/Documentation suite" + + Forward compatibility (HATEOAS): + description: | + This is a self-referencing REST API that follows the principles of HATEOAS. Other than the + initial `GET` to `/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 licensed API calls. + + 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. + + Operator privileges: + description: | + 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. + + Field specifiers (in general): + $ref: "cardholdersApi.yaml#/x-spectacle-topics/Field specifiers" + + Event field specifiers: + + description: | + 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](#definition-Event-detail), 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](cardholders.html#operation--api-personal_data_fields-get). 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 8.30 you can also add `details`. Make sure you have a separating comma after + `defaults` or `cardholder.pdf_XXXX`. + + An event detail string can be kilobytes long so we left it optional. It is the only field in + the details page that is not in the search results, by the way, so if you are running 8.20 or + later you can add it to your search results and you will not need the details page. + + | Server version | Valid fields parameter values + | ------------ | -------------- + | Older than 8.20 | defaults,cardholder.pdf_XXXX + | 8.20 to 8.30 | defaults,cardholder.pdf_XXXX
or
defaults,details
or
defaults,cardholder.pdf_XXXX,details + | 8.40 and later | any + +x-common-blocks-this-is-made-up: + fds_ignored: &FIELDSDESC_SUM + description: | + 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. + + Treat the string matches as case-sensitive. + + fds840_ignored: &FIELDSDESC_SUM_840 + description: | + 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. + + Treat the string matches as case-sensitive. + + The string must not contain any spaces. Just alphanumerics, underscores, commas, and dots. + + Added in 8.40. + + fdd_ignored: &FIELDSDESC_DET + description: | + Return 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. + + fdd840_ignored: &FIELDSDESC_DET_840 + description: | + Return 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. + + The string must not contain any spaces. Just alphanumerics, underscores, commas, and dots. + + Added in 8.40. + + divfdd_ignored: &DIVFIELDSDESC + description: | + 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. + + item_ignored: &ITEMFIELDS + in: query + required: false + type: string + enum: [href, id, name, type, division, serverDisplayName, notes] + division_ignored_but_must_be_unique: &DIVFIELDS + in: query + required: false + type: string + enum: [href, id, name, parent, visitorManagement] + + notes_ignored: &NOTES + type: string + example: "Multi-line text..." + description: | + Because of their potential size, notes are only available by request. Use the 'fields' + parameter: + + `?fields=defaults,notes,...` + + notes_this_keyword_can_be_anything: &TYPE_NAME + type: string + example: "Cardholder" + description: | + 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. + + eventType_ignored: &EVENTTYPE + type: object + description: | + ID and name of the event's or alarm's type. There is a long list of them at + [/events/groups](#operation--api-events-groups-get). + + 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. + + properties: + id: + type: string + description: | + The alphanumeric ID of the event type. You can use this ID in the `type` parameter when + searching for events with [/events](#operation--api-events-get). + example: "23035" + name: + type: string + description: The name of the event type. + example: "Forced door" + + canon_ignored: &CANON + type: string + description: | + 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. + +security: + - "API key": [] + +###################################################################### +parameters: + + alarmid: + name: "id" + in: path + required: true + type: string + description: | + The ID of the alarm. This is not really a parameter you should insert into your request. + Instead you should get the URL of this call from other results: the alarm summary or details, + in this case. + + alarmfields: + name: fields + in: query + type: string + required: false + + enum: [ href, id, time, message, source, type, eventType, priority, state, active, division, notePresets, view, comment, acknowledgeWithComment, acknowledge, processWithComment, process, details, history, instruction, cardholder, event ] + + description: | + Sets the fields you want in your results. Separate fields with commas. + + New to 8.40. + + eventfields: + name: fields + in: query + type: string + required: false + enum: [ defaults, details, cardholder.pdf_*, href, id, serverDisplayName, time, message, occurrences, priority, alarm, operator, source, group, type, eventType, division, cardholder, entryAccessZone, exitAccessZone, door, accessGroup, card, modifiedItem, lastOccurrenceTime, previous, next, updates, location] + + description: | + Sets the fields you want in your results. + + This is the list of valid fields for 8.40, but it is different for older server versions, so + check with the topic [on event field specifiers](#topic-Event-field-specifiers). + +paths: + +###################################################################### + + /api/alarms: + get: + tags: + - Alarms + summary: Get current alarms + description: | + 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 you may then + follow to long-poll for live updates to alarms. + + The alarm summary only contains unprocessed alarms, but you can still access + processed alarms by finding them in the [event summary](#definition-Event-summary) + and following its `alarm.href` link to the alarm details. + + 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. + + Do not code this URL into your application. Take it from `alarms.alarms.href` in the + results of `GET /api`. + + parameters: + - $ref: "#/parameters/alarmfields" + responses: + 200: + description: Success + schema: {$ref: '#/definitions/Alarm search'} + 403: + description: The site does not have a RESTEvents licence. + + /api/alarms/updates: + get: + tags: + - Alarms + summary: Get changes to alarms (or wait) + description: | + Long poll this link 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) the `updates` array in its response will be empty. + + 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 its load. You will not miss any updates: each response contains + everything that happened since the previous call. + + 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 + succesfully. + + parameters: + - $ref: "#/parameters/alarmfields" + responses: + 200: + description: Success + schema: + $ref: '#/definitions/Alarm updates' + 403: + description: The site does not have a RESTEvents licence. + + /api/alarms/{id}: + get: + tags: + - Alarms + summary: Get details of an alarm + description: Full details for an alarm. Follow the `href` in the alarm summary to get here. + parameters: + - $ref: "#/parameters/alarmid" + - $ref: "#/parameters/alarmfields" + responses: + 200: + description: "Success" + schema: { $ref: '#/definitions/Alarm detail' } + 404: { description: "The alarm ID is invalid or you do not have privileges for the alarm." } + + /api/alarms/{id}/view: + post: + summary: Mark an alarm as viewed + description: Mark the alarm as viewed. Follow the `view` link in the alarm summary to get here. + tags: + - Alarms + parameters: + - $ref: "#/parameters/alarmid" + - name: body + description: Optional comment. + in: body + required: false + schema: { $ref: '#/definitions/Alarm update request' } + responses: + 200: { description: "Success" } + 404: { description: "Alarm ID is invalid or you do not have privileges for the alarm." } + + /api/alarms/{id}/comment: + post: + summary: Add a comment to an alarm + description: Follow the `comment` link in the alarm summary to get here. + tags: + - Alarms + parameters: + - $ref: "#/parameters/alarmid" + - name: body + description: The comment string. + in: body + required: true + schema: { $ref: '#/definitions/Alarm update request' } + responses: + 200: { description: "Success" } + 404: { description: "Alarm ID is invalid or you do not have privileges for the alarm." } + + /api/alarms/{id}/acknowledge: + post: + summary: Mark an alarm as acknowledged + description: | + Follow the `acknowledgeWithComment` or the `acknowledge` link in the alarm summary to get + here. + tags: + - Alarms + parameters: + - $ref: "#/parameters/alarmid" + - name: body + description: Optional comment. + in: body + required: false + schema: { $ref: '#/definitions/Alarm update request' } + responses: + 200: { description: "Success" } + 404: { description: "Alarm ID is invalid or you do not have privileges for the alarm." } + + /api/alarms/{id}/process: + post: + summary: Mark an alarm as processed + description: | + Follow the `processWithComment`, `forceProcess`, or `process` link in the alarm to get here. + tags: + - Alarms + parameters: + - $ref: "#/parameters/alarmid" + - name: body + description: Optional comment. + in: body + required: false + schema: { $ref: '#/definitions/Alarm update request' } + responses: + 200: { description: "Success" } + 404: { description: "Alarm ID is invalid or you do not have privileges for the alarm." } + +####################################################################### + + /api/events: + post: + tags: + - Events + summary: Add an event + description: | + 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](#definition-Event-POST-body). + + Events are immutable: you cannot PATCH or DELETE them after you create them. + + #### Usable event types + + You must supply an [event type](#operation--api-events-groups-get). 8.10 ships with 30 + external event types you can use, each in its own event type group. + + 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! + + #### Usable source items + + Every event needs a source item. You can let the server pick one for you (it will use the + REST Client item identified by your API key) or you can use any item that has an 'Event + Response' tab in its Configuration Client window (which is most of them). Our existing + integrations use items like doors, external system items, and cameras. + + If you allow the server to default to your REST client item as the source, or if specify it + yourself, your operator must have 'Create events and alarms' in at least one division for + the call to succeed. Any division will do. Otherwise you'll receive a 403. + + If you specify a source item to a server running 8.90 or later and that item is not your + REST Client item, your operator must have the 'Create events and alarms' privilege in that + item's division. In older versions it was enough for your operator to have that privilege + in any division. + + Cardholders cannot be event sources because they do not have an 'Event Response' tab, but... + + #### Related items + + Along with a source, you can link other items to the events you create. If you link a + cardholder, for example, your events will show on an activity report generated for that + cardholder. To link an item your operator must have a privilege that allows viewing it + ('View cardholders' for cardholders or 'View site' for most other item types). + + #### Action plans + + An event can fire an action plan, which will + * set the priority if the REST client did not set one in the body of the POST, and + * run a macro on the server. + + Macros are extremely powerful, and a thorough treatment requires more room than we have, so + it is sufficient to say that you should not aim for a dramatic first test. A good first + result is to turn on a virtual output made for the purpose. Just make sure that it will not + trigger another macro because it is possible to create loops, causing havoc. + + You do not pick an action plan to run when you POST your event. Command Centre does that in + three steps: + + 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 on 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. + + parameters: + - name: "not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Event POST body'} + description: | + You can specify many things on an event but the only mandatory field is the type. When + you are developing, start with just that. + responses: + 201: + description: "Success." + headers: + location: + type: string + format: url + description: The href of the new event. + 204: + description: | + Success with no event created, probably because the priority in the action plan was + zero. + 400: + description: | + 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. + + 403: + description: | + 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. + + get: + tags: + - Events + summary: Search events + description: | + 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](#event-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: + - name: "top" + in: query + required: false + type: integer + minimum: 1 + maximum: 10000 + default: 1000 + description: Sets the maximum number of events to return per page. + - name: "after" + in: query + required: false + type: string + format: date-time + description: | + 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`. + - name: "before" + in: query + required: false + type: string + format: date-time + description: | + 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. + - name: "source" + in: query + required: false + type: string + description: | + Restricts events to those whose source item has this ID. Separate multiple IDs with + commas. Use the [items API](#tag-Items) to search Command Centre's items. + - name: "type" + in: query + required: false + type: string + description: | + Restricts events to those whose type has this ID. Separate multiple IDs with commas. + + The results may include other event types if you also use the `group` parameter, below. + + Use the [event groups call](#operation--api-events-groups-get) to see all event types and + groups. + + 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. + + - name: "group" + in: query + required: false + type: string + description: | + 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](#operation--api-events-groups-get) to see all event types and + groups. + - name: "cardholder" + in: query + required: false + type: string + description: "Restricts events to those associated with the cardholder that has this Command Centre ID. Separate multiple IDs with commas.\n\nExample: `cardholder=325`" + - name: "division" + in: query + required: false + type: string + description: "Restricts events to those in this division (including its child divisions). Separate multiple IDs with commas.\n\nExample: `division=2,101`\n\nA more secure option is to set the operator's privileges so that it only has access to those divisions." + - name: "relatedItem" + in: query + required: false + type: string + description: "Restrict events to those associated with the item that has this Command Centre ID. Separate multiple IDs with commas.\n\nExample: `relatedItem=3,102`" + - $ref: "#/parameters/eventfields" + - name: "previous" + in: query + required: false + type: boolean + default: false + description: | + 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. + - name: "pos" + in: query + required: false + type: integer + minimum: 0 + description: | + Restricts events to those with event IDs greater than this parameter (or less than and + including, if you set `previous=true`). + + 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. + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Event search' } + 403: + description: The site does not have a RESTEvents licence. + + /api/events/updates: + get: + tags: + - Events + summary: Get new events (or wait) + description: | + 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](#event-use-cases). In + particular, sleep between calls to reduce load on the server. + + The way this call picks events is very different from `/api/events`: + + - If you follow the link at `events.updates.href` in the results of `GET /api`, you will + receive the first events that arrive after you make the call and that meet your other + search criteria. + + - If you follow the `updates` link from the results of `GET /api/events` or `GET + /api/events/updates`, the link will contain a bookmark in the `pos` parameter that causes + the server to return the first events that arrive or arrived after that bookmark (and that + meet your search criteria, of course). + + 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](#operation--api-events-get). + + 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: + - name: "top" + in: query + required: false + type: integer + minimum: 1 + maximum: 10000 + description: Sets the maximum number of events to return per page. + - name: "deadline" + in: query + required: false + type: integer + minimum: 1 + maximum: 86400 + description: | + 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. + - name: "after" + in: query + required: false + type: string + format: date-time + description: | + 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](#operation--api-events-get), 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. + + - name: "before" + in: query + required: false + type: string + format: date-time + description: | + 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](#operation--api-events-get), 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. + + - name: "source" + in: query + required: false + type: string + description: Restricts events to those whose source item has this ID. Separate multiple IDs with commas. Use `/api/items` to search Command Centre's items. + - name: "type" + in: query + required: false + type: string + description: | + Restricts events to those with this event type ID. Separate multiple IDs with commas. + + Use `/api/events/groups` 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. + - name: "group" + in: query + required: false + type: string + description: | + 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. + + - name: "cardholder" + in: query + required: false + type: string + description: "Restricts events to those associated with the cardholder with this Command Centre ID. Separate multiple IDs with commas.\n\nExample: `cardholder=325`" + - name: "division" + in: query + required: false + type: string + description: "Restricts events to those in the division with this ID and its descendant divisions. Separate multiple IDs with commas.\n\nExample: `division=2,101`" + - $ref: "#/parameters/eventfields" + - name: "pos" + in: query + required: false + type: integer + minimum: 0 + description: | + Restricts events to those with event IDs greater than this parameter. + + 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. + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Event search' } + 403: + description: The site does not have a RESTEvents licence. + + /api/events/{id}: + get: + tags: + - Events + summary: Get details of an event + description: | + 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: + - name: "id" + in: path + required: true + type: string + description: The ID of the event. + - $ref: "#/parameters/eventfields" + responses: + 200: + description: "Success" + schema: { $ref: '#/definitions/Event detail' } + 403: { description: The site does not have a RESTEvents licence. } + 404: + description: | + That is not the URL of an event, or it is but you do not have privileges to read events + in its division. + + /api/events/groups: + get: + tags: + - Events + summary: List event types + description: | + 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`. + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Event groups' } + 403: { description: The site does not have the RESTEvents or RESTCreateEvents licence. } + +###################################################################### + + /api/divisions: + post: + tags: + - Divisions + summary: Create a division [coming soon] + description: | + Creates a new division. + + The POST expects a document in the same format as the [the division + detail](#definition-Division) but with far fewer fields. An example is [this POST + example](#definition-Division-PATCH-and-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`. + + `/api` only shows API features for which you have the necessary licence. + + Creating a division requires the RESTCardholders licence. + + It is coming to a future version of Command Centre. + + parameters: + - name: "this name not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Division PATCH and POST example'} + description: | + The only required field here is the new division's parent. All divisions (except the + root) must have a parent. + + responses: + 201: + description: | + Success. Check the response body for feedback about your request. + headers: + location: + type: string + format: url + description: The href of the new division. + 400: + description: | + 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. + + 403: + description: | + 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. + + /api/divisions/{operation}: + get: + tags: + - Divisions + summary: List divisions + description: | + 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: + - name: "top" + in: query + required: false + description: Sets the maximum number of divisions to return per page. + type: integer + minimum: 1 + - $ref: "cardholdersApi.yaml#/parameters/sort" + - name: "fields" + <<: [*DIVFIELDSDESC, *DIVFIELDS] + responses: + 200: + description: Success + schema: + $ref: '#/definitions/Divisions' + 403: { description: The site does not have a REST licence. } + + /api/divisions/{id}: + get: + tags: + - Divisions + summary: Get details of a division + description: Details of a division. Follow the `href` in a division summary to get here. + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the division. + - name: "fields" + <<: [*DIVFIELDSDESC, *DIVFIELDS] + responses: + 200: + description: "Success" + schema: { $ref: '#/definitions/Division' } + 403: { description: The site does not have a REST licence. } + 404: { description: "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)." } + + patch: + tags: + - Divisions + summary: Update a division [coming soon] + description: | + 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](#definition-Division) but with fewer valid fields. An example is [this PATCH + example](#definition-Division-PATCH-and-POST-example). + + Patching divisions is coming to a future version of Command Centre. + + parameters: + - name: "this name not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Division PATCH and POST example'} + description: | + There are no mandatory fields. + + responses: + 200: + description: | + Success. The response body will contain feedback from the server about your PATCH. + 204: + description: "Success." + 400: + description: | + 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. + 403: + description: | + 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. + + 404: + description: | + 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](#operation--api-divisions--operation--get). + + 409: + description: | + The item is locked for editing by another operator. The body of the response will + tell you which operator is holding the lock. + + delete: + tags: [ Divisions ] + summary: Remove a division + description: | + This call removes a division from Command Centre. + + Deleting divisions will be possible in a future version of Command Centre. + responses: + 204: { description: "Success." } + 400: + description: | + 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). + 403: + description: | + The operator has the permission to view the division but not delete it, or the server is + not licensed for the operation. + 404: + description: | + That is not the URL of a division, or the operator is not privileged to view it. + +###################################################################### + + /api/items: + get: + tags: + - Items + summary: Search items + description: | + 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: + - $ref: "cardholdersApi.yaml#/parameters/name" + - name: "division" + in: query + required: false + type: string + description: | + Only returns items that are in these divisions or their subdivisions. Use the divisions' + short alphanumeric IDs, separateed by commas. + - name: "type" + in: query + required: false + type: string + description: | + 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. + - name: "top" + in: query + required: false + description: Sets the maximum number of items to return per page. + type: integer + minimum: 1 + default: 100 + - $ref: "cardholdersApi.yaml#/parameters/sort" + - name: "fields" + <<: [*FIELDSDESC_SUM, *ITEMFIELDS] + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Item search' } + 403: { description: The site does not have a REST licence. } + + /api/items/{id}: + get: + tags: + - Items + summary: Get details of an item + description: | + This returns some basic fields for one item. It returns the same information as the [item + search](#operation--api-items-get) plus the item's division. + + Added in 8.40. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the item. + - name: "fields" + <<: [*FIELDSDESC_SUM, *ITEMFIELDS] + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Item detail' } + 404: + description: | + That is not the href of an item. At least not one that your operator has the privilege + to view. + + /api/items/types: + get: + tags: + - Items + summary: List item types + description: | + 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`. + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Item types' } + 403: { description: The site does not have a REST licence. } + + /api/items/updates: + post: + tags: [Items] + summary: Subscribe to status updates + description: | + 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. + + Your operator must have view privileges on every item in the subscription. + + 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. + + parameters: + - name: "name not rendered for body params" + in: body + description: | + 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. + schema: {$ref: "#/definitions/Item update subscription"} + example: { "itemIds": ["508", "526"] } + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Item update' } + 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" ] + } + ], + "next": { + "href": "https://localhost:8904/api/items/updates?bookmark=3ec613a1-de01c6e_0" + } } + + 400: { description: The server could not parse the request body. Check your JSON. } + 401: { description: The operator does not have privilege to view the monitored items. This will change to 403 in a future version of Command Centre. } + 403: { description: The site does not have the RESTStatus licence. } + + get: + tags: [Items] + summary: Retrieve status updates + description: | + Collects status updates from a subscription created using the [/api/items/updates + POST](#operation--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 too much time has passed since the server + sent the link, or the server has restarted. In either case the server 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](#operation--api-items-updates-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 data you have seen + already (when an override is sent to an item, or some aspect of an item changes that your + operator does not have the privilege to view, for example). These two behaviours are not + harmful, but also not particularly helpful, so future versions may differ. + + parameters: + - name: "bookmark" + in: query + required: true + type: string + description: | + Identifies your subscription and your position in the change list. You do + not need to set this parameter: it will be in the link that the server sends back to + you. + + responses: + 200: + description: Success + schema: { $ref: '#/definitions/Item update' } + 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" ] + } + ], + "next": { + "href": "https://localhost:8904/api/items/updates?bookmark=3ec613a1-de01c6e_0" + } } + 404: { description: "The subscription does not exist, which probably means you waited too long between calls." } + 403: { description: "The site does not have a REST licence." } + +#################################################################################################### +#################################################################################################### +definitions: + + Alarm search: + description: | + An array of alarm summaries, and either a 'next' or an 'updates' link you should follow to + retrieve more alarms. + type: object + properties: + alarms: + type: array + description: An array of alarm summaries. + items: + { $ref: '#/definitions/Alarm summary' } + next: + type: object + description: The link to the next page of alarms. Missing if you have retrieved all the current alarms. + properties: + href: { type: string } + example: + href: "https://localhost:8904/api/alarms/next?start=92143&pos=61320" + updates: + type: object + description: The link to follow to long-poll for alarm changes. Missing if you have not yet retrieved all the current alarms. + properties: + href: { type: string } + example: + href: "https://localhost:8904/api/alarms/updates?id=92143.1" + + Alarm updates: + type: object + properties: + updates: + type: array + description: An array of summaries of alarms created or modified since the previous call. + items: + { $ref: '#/definitions/Alarm summary' } + next: + type: object + description: Follow this link to perform another long poll. + properties: + href: + type: string + example: "https://localhost:8904/api/alarms/updates?id=10135" + + Alarm summary: + description: "`/api/alarms` returns an array of these, and `/api/alarms/{id}` returns one with more fields." + type: object + properties: + href: + type: string + format: url + description: A link to the details of this alarm. + example: "https://localhost:8904/api/alarms/10135" + id: + type: string + description: An alphanumeric identifier for this alarm, unique to the server. + example: "10135" + time: + type: string + description: The time the alarm occurred. + example: "2016-02-18T19:21:52Z" + message: + type: string + description: The alarm's message. + example: "External bulk loading bay door has been forced" + source: + type: object + description: The ID and href are new to 8.10. + properties: + id: + type: string + description: | + The alphanumeric ID of the alarm's source item. + example: "1321" + name: + type: string + description: The name of the alarm's source item at the time of the event. + example: "External bulk loading bay door" + href: + type: string + format: url + description: Link to the source item. + example: "https://localhost:8904/api/doors/1321" + type: + type: string + deprecated: true + description: | + The name of this alarm's event type. + + example: "Forced door" + eventType: + <<: *EVENTTYPE + priority: + type: integer + description: Numeric priority. 9 is critical and 0 is not an event. + minimum: 0 + maximum: 9 + example: 8 + state: + type: string + description: The state of the alarm. Alarms start at "unacknowledged". + enum: [ unacknowledged, acknowledged, processed ] + example: "unacknowledged" + active: + type: boolean + description: Clients cannot process active alarms. + example: false + division: + type: object + description: The division entity representing the division of the alarm. GET the href for full details. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/divisions/2" + event: + type: object + description: | + 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. + properties: + href: + type: string + format: url + description: | + Link to the event page for this alarm. GET it for the fields found on an event + that are not on an alarm. + example: "https://localhost:8904/api/events/10135" + notePresets: + type: array + items: + type: string + description: 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. + example: + - "False alarm confirmed by surveillance" + - "Security staff dispatched" + view: + type: object + description: | + POST an alarm update request JSON object to the href to indicate the operator has viewed + the alarm. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/view" + comment: + type: object + description: | + POST an alarm update request JSON object to the href to place an arbitrary alarm note + against the alarm. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/comment" + acknowledge: + type: object + description: | + 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. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/acknowledge" + acknowledgeWithComment: + type: object + description: | + 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. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/acknowledge" + process: + type: object + description: 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). + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/process" + processWithComment: + type: object + description: 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). + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/process" + forceProcess: + type: object + description: | + 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). + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/alarms/92210/process" + + Alarm detail: + description: | + [/api/alarms/{id}](#operation--api-alarms--id--get) 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. + allOf: + - type: object + properties: + details: + type: string + description: | + The full alarm details text. This may be up to 2048 UTF-8 characters, each of which + could (theoretically) be four bytes long. + example: "Forced door" + history: + type: array + description: An array of alarm history entries. Missing if there is no history. + items: { $ref: '#/definitions/Alarm history entry' } + instruction: + type: object + description: GET this link to retrieve the alarm instruction body inside an HTML document. Missing if there is no alarm instruction for this alarm. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/alarms/92210/instructions" + cardholder: + type: object + description: The cardholder entity associated with this alarm. GET the href for full details. Missing if this alarm does not have an associated cardholder. + properties: + href: { type: string, format: url, example: https://localhost:8904/api/cardholders/325 } + name: + type: string + description: | + 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. + example: "Smith, Jane" + firstName: + type: string + description: The current value of the firstName field of this cardholder. Added in 8.20. + example: "Jane" + lastName: + type: string + description: The current value of the lastName field of this cardholder. Added in 8.20. + example: "Smith-Jones" + - $ref: "#/definitions/Alarm summary" + + Alarm history entry: + type: object + properties: + time: + type: string + description: The time the history entry was added. + example: "2016-02-18T19:21:52Z" + action: + type: string + description: The type of the history entry. + enum: [ legacy, comment, acknowledge, process, acknowledgeActive, escalated, viewed ] + example: "viewed" + comment: + type: string + description: The added comment, or a textual description of some occurrence related to the alarm. + example: "Operator viewed alarm properties" + operator: + type: object + description: The operator that created the history event. + example: + name: "System Operator" + + Alarm update request: + type: object + properties: + comment: + type: string + description: "Optional for some methods that update alarms. Contains a comment placed by the operator." + example: "Alarm was adequately explained." + + Event search: + description: "`/api/events` and `/api/events/updates` return this structure." + + type: object + properties: + events: + type: array + description: A list of event summaries. + items: { $ref: "#/definitions/Event summary" } + previous: + type: object + description: | + 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. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/events/next?previous=True&pos=61320" + next: + type: object + description: | + 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. + + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/events/next?pos=61320" + + updates: + type: object + description: | + This is a link to an [updates](#operation--api-events-updates-get) 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. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/events/updates?pos=61320" + + Event summary: + type: object + description: | + `/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. + + properties: + href: + type: string + format: url + description: A link to this event's details. + example: "https://localhost:8904/api/events/61320" + id: + type: string + description: An alphanumeric identifier for this event, unique to the server. + example: "61320" + serverDisplayName: + type: string + description: | + 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. + example: "ruatoria.satellite.int" + time: + type: string + format: date-time + description: The time the event occurred. + example: "2016-02-18T19:21:52Z" + message: + type: string + description: "The event's message." + example: "Operator logon failed for FT Workstation on GNZ-PC1439" + occurrences: + type: integer + description: | + 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. + minimum: 2 + example: 2 + priority: + type: integer + description: Numeric priority. 9 is critical and 0 is not an event. + minimum: 0 + maximum: 9 + example: 3 + alarm: + type: object + description: | + 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. + properties: + state: + description: Alarms start unacknowledged. Acknowledging or processing them changes that. + type: string + enum: [ "unacknowledged", "acknowledged", "processed" ] + example: "unacknowledged" + href: + type: string + format: url + description: Link to the alarm entity corresponding to the event. GET the href for full alarm details. + example: "https://localhost:8904/api/alarms/61320" + operator: + description: | + The href and name of the operator 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). + type: object + properties: + href: + type: string + format: url + description: Link to the operator who caused this event by editing an item. New in 8.00. + example: "https://localhost:8904/api/cardholders/325" + name: + type: string + description: The name the operator held at the time. New in 8.40. + example: "Chong, Marc" + source: + type: object + description: ID and name of the source of the event, as recorded at the time of the event. + properties: + id: + type: string + description: | + 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. + example: "321" + name: + type: string + description: This could be different from the current name of the source item. + example: "FT Workstation on GNZ-PC1439" + href: + type: string + format: url + description: Link to the source item. New in 8.00. + example: "https://localhost:8904/api/items/321" + group: + type: object + description: | + 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](#operation--api-events-groups-get). + properties: + id: + type: string + description: | + 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. + example: "35" + name: + type: string + description: The name of the event group. + example: "Invalid Logon" + type: + type: object + description: | + ID and name of the event's type. There is a long list of them at + [/events/groups](#operation--api-events-groups-get). + properties: + id: + type: string + description: | + 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](#operation--api-events-get). + example: "601" + name: + type: string + description: The name of the event type. + example: "Operator logon failed" + eventType: + <<: *EVENTTYPE + example: + id: "601" + name: "Operator logon failed" + division: + type: object + description: | + ID, name, and href of the event's division (which is the division of the event's source + item, for most event types). + properties: + id: + type: string + description: | + 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. + example: "2" + href: + type: string + format: url + description: The link to the division item. + example: "https://localhost:8904/api/divisions/2" + name: + type: string + description: The division's name. Added in 8.40. + example: "Root division" + + cardholder: + type: object + description: | + 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. + properties: + href: + type: string + format: url + description: | + Link to the cardholder entity representing the cardholder of the event. GET the href + for full details. + example: "https://localhost:8904/api/cardholders/325" + id: + type: string + description: The alphanumeric ID of the cardholder associated with this event. + example: "325" + name: + type: string + description: | + 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. + example: "Bruce, Jennifer" + firstName: + type: string + description: The current value of the firstName field of this cardholder. Added in 8.20. + example: "Jennifer" + lastName: + type: string + description: The current value of the lastName field of this cardholder. Added in 8.20. + example: "Caitlin" + + entryAccessZone: + description: | + 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. + + properties: + href: + type: string + format: url + description: | + 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'). + example: "https://localhost:8904/api/access_zones/333" + name: + type: string + description: | + 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. + + example: "Brookwood showroom" + id: + deprecated: true + type: string + description: Deprecated. + example: "333" + + exitAccessZone: + description: | + 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. + + properties: + href: + type: string + format: url + description: | + 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'). + example: "https://localhost:8904/api/access_zones/913" + name: + type: string + description: | + 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. + example: "Compressor room" + id: + deprecated: true + type: string + description: Deprecated. + example: "913" + + door: + description: | + 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. New in 8.10. + + properties: + href: + type: string + format: url + description: | + 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'). + example: "https://localhost:8904/api/doors/745" + name: + type: string + description: The name the door had when the event occurred. + example: "Main hoist door" + + accessGroup: + description: | + 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). + type: object + properties: + href: + type: string + format: url + description: Link to the access group related to this event. + example: "https://localhost:8904/api/access_groups/352" + + card: + description: | + 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. + + type: object + properties: + facilityCode: + type: string + description: | + The card's facility code at the time of the event expressed as one letter followed by + up to five digits. + example: "A12345" + number: + type: string + description: | + 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. + + example: "78745" + issueLevel: + type: integer + example: 1 + description: | + The issue level of the card at the time. + + modifiedItem: + description: | + The href and type of the item that an operator created, changed, or deleted, for those + kinds of events. + + New in 8.40. + type: object + properties: + href: + type: string + format: url + description: | + Link to the item that this event modified. Watch for 404s: this link will be here + even for deleted items. + example: "https://localhost:8904/api/cardholders/325" + type: + type: object + properties: + id: + type: string + example: "1" + description: | + A short alphanum identifying the item's type. + name: + type: string + example: "Cardholder" + description: | + 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. + + next: + description: | + 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. + + The `next` link on the last event in a result set will be the same as the `next` link + outside the results. + + New to 8.70. + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/events?pos=61320" + previous: + description: | + 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. + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/events?pos=61320&previous=True" + updates: + description: | + The URL to the [updates](#operation--api-events-updates-get) 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. + + The `updates` link on the last event in a result set will be the same as the `updates` + link outside the results, since they both indicate the event that will be at the top of + the next result set. + + New to 8.70. + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/events/updates?pos=61320" + + Event detail: + description: | + [/api/events/{id}](#operation--api-events--id--get) 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](#definition-Alarm-summary), this example is forced: no + actual Command Centre event will contain all of these fields. + + allOf: + - type: object + properties: + lastOccurrenceTime: + type: string + format: date-time + description: | + 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. + example: "2016-02-18T19:21:59Z" + details: + type: string + description: | + The full alarm details text. This may be up to 2048 UTF-8 characters, each of which + could (theoretically) be four bytes long. + example: "Originating IP address: 192.168.2.3" + location: + type: object + description: | + This block gathers together information that is contained 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 these three + fields: + + * The cardholder whose location the event contains, + * the access zone that they started in before the event, and + * the item that indicates their location after the event. + + The location block will only be in a result if you ask for it using the `fields` query + parameter. + + properties: + type: + type: string + enum: [ moved, observed, denied ] + example: "moved" + description: | + + 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. + + cardholder: + type: object + description: | + + 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. + + properties: + name: + type: string + description: "The cardholder's name at the time of the event." + example: "Jackson" + href: + type: string + format: url + description: | + 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. + example: "https://localhost:8904/cardholders/325" + + beforeLocation: + description: | + 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. + + example: { + href: "https://localhost:8904/api/access_zones/333", + name: "Lvl 1 lift lobby", + canonicalTypeName: "accesszone"} + + afterLocation: + description: | + 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](#operation--api-items-types-get). + + 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: { outside: true } + + - $ref: "#/definitions/Event summary" + + Event POST body: + required: [type, eventType] + properties: + type: + description: | + This is a mandatory field in an event. Without it, the POST will fail. + + It is mandatory because the server cannot assume a reasonable default. It can for the + others. + + If your server is running 8.90 or later, `eventType` is a synonym. If you send both a `type` block + and an `eventType` block it will use `eventType`. + + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/events/types/4000 + description: | + Take this href from the list of [event types](#operation--api-events-groups-get). + 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. + eventType: + description: | + Synonym for `type`, introduced in 8.90. + properties: + href: + type: string + format: url + example: https://localhost:8904/api/events/types/4000 + source: + description: | + 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. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/doors/745 + description: | + Get the href using the API controller for that item type (such as + [doors](rest.html#tag-Doors)) or from `/items`. + priority: + type: integer + minimum: 1 + maximum: 9 + example: 2 + description: | + 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. + time: + type: string + format: date-time + description: | + 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](cardholders.html#dates-and-times). + + If you do not send this, the server will use the time that it received your request + (its "now"). + example: "2019-02-21T14:55:00Z" + message: + type: string + example: "Glass break detected in southwest sauna" + description: | + 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. + details: + type: string + description: | + 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. + example: "" + cardholder: + description: | + If you wish to attach a cardholder to your event, link it here. Reports can show or + filter by the cardholder. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/cardholders/325 + description: | + This can be the href of any cardholder to which your operator has view access. Get + the href from the [cardholders controller](cardholders.html#tag-Cardholders). + operator: + description: | + If you wish to attach an operator to your event, link it here. Like the cardholder, + reports can show or filter by the operator. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/cardholders/5398 + description: | + 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). + entryAccessZone: + description: | + 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. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/access_zones/333 + description: | + This can be the href of any access zone to which your operator has view access. Get + the href using the [access zones controller](rest.html#tag-Access-Zones) or from + `/items`. + accessGroup: + description: | + 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. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/access_groups/352 + description: | + This can be the href of any access group to which your operator has view access. Get + the href using the [groups](cardholders.html#tag-Access-groups) or [items](#tag-Items) + controller. + lockerBank: + description: | + 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. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/locker_banks/4566 + description: | + This can be the href of any locker bank to which your operator has view access. Get + the href using the [locker banks](cardholders.html#tag-Lockers) or + [items](#tag-Items) controller. + locker: + description: | + 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. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/lockers/3456 + description: | + This can be the href of any locker your operator can view. Get the href from the + [items](#tag-Items) controller or the `lockers` field of [a locker + bank](cardholders.html#definition-Locker-bank-detail). + door: + description: | + If you wish to link a door to your event for later extraction or a report filter, do it + here. + type: object + properties: + href: + type: string + format: url + example: https://localhost:8904/api/doors/745 + description: | + This can be the href of any door your operator can see. Get the href using the + [doors](rest.html#tag-Doors) or [items](#tag-Items) controller. + + Event groups: + description: Calls to `/api/events/groups/` return this object, which is a named array of groups of event types. + type: object + properties: + eventGroups: + type: array + description: An array of event group objects. There will be about 150. Most groups contain fewer than 100 event types; one contains around 200. + items: + type: object + properties: + id: + type: string + description: The alphanumeric ID of the event group. Use this ID in the `group` filter when requesting events. + example: "35" + name: + type: string + description: The name of the event group. + example: "Invalid Logon" + eventTypes: + type: array + description: An array of all the event types in the group. + items: + type: object + properties: + id: + type: string + description: The alphanumeric ID of the event type. Use this ID in the `type` filter when requesting events. + name: {type: string} + example: + - 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 + + Divisions: + type: object + description: | + Calls inside `/api/divisions/` return this object, which is simply a named array of + objects each containing some information about a division. + + properties: + results: + type: array + description: An array of division objects. + items: + { $ref: '#/definitions/Division' } + next: + description: | + A site generally does not have too many divisions. You should be able to collect them all + in one request by using the `top` parameter. If not, follow this link to get the next + page of divisions. + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/divisions/view_events?skip=10" + + Division: + type: object + description: | + 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. + properties: + href: + type: string + format: url + description: A self reference. + example: "https://localhost:8904/divisions/2" + id: + type: string + description: | + The alphanumeric ID of the division. Use this ID in the `division` filter when requesting + events. + example: "2" + name: + type: string + description: The division's name. + example: "Root division" + description: + type: string + description: The division's description. New in 8.50. Not sent by default; ask for it with `fields`. + example: "Contains all other divisions" + serverDisplayName: + $ref: "cardholdersApi.yaml#/x-common-blocks/server" + parent: + description: "An object containing an href to the division entity representing the current division's parent. GET the href for full details." + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/divisions/2" + visitorManagement: + description: | + 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'). + type: object + properties: + active: + type: boolean + example: true + description: | + If present and true, this division has its own visitor management configuration. + Otherwise it uses its parent's. That is also indicated by the presence or absence of + the `visitorTypes` block. + visitortypes: + type: array + description: | + 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). + + items: + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/divisions/2/visitor_types/925" + description: | + 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. + accessGroup: + type: object + description: | + 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. + properties: + name: + type: string + example: "Visitor access group 1" + href: + type: string + format: url + example: "https://localhost:8904/api/access_groups/925" + hostAccessGroups: + type: array + description: | + 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. + items: + type: object + properties: + accessGroup: + type: object + properties: + name: + type: string + example: "Host access group 1" + href: + type: string + format: url + example: "https://localhost:8904/api/access_groups/938" + visitorAccessGroups: + description: | + 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. + + type: array + items: + type: object + properties: + accessGroup: + type: object + description: | + 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. + properties: + name: + type: string + href: + type: string + format: url + example: [ + { + "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" + } + } ] + + Division PATCH and POST example: + type: object + description: | + 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. + + properties: + name: + type: string + description: | + 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. + + example: "Long division" + description: + type: string + description: The division's description. + example: "Quatermasters" + notes: + type: string + description: | + A string, able to me much longer than `description`, suitable for holding notes about the + division. + example: "A very long string." + parent: + description: | + 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. + + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/divisions/2" + + Item search: + type: object + description: Calls to `/api/items` return this object. + properties: + results: + description: A list of item objects, each containing the alphanumeric ID of the item, its name, and its type. + type: array + items: { $ref: "#/definitions/Item summary" } + example: + - 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: + type: object + description: An href to the next page of results. Missing if there are no more results. + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/items?pos=2" + + Item summary: + type: object + description: | + `/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. + properties: + id: + type: string + description: | + 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. + example: "325" + name: { type: string, example: "Brick, Eva" } + type: + type: object + description: The `type` object contains the ID and name of the item's type. + properties: + id: + type: string + description: | + Use this ID in the `type` filter when requesting items if you wish to restrict the + results to items of a certain type. + example: "1" + name: + <<: *TYPE_NAME + canonicalTypeName: + <<: *CANON + example: "cardholder" + serverDisplayName: + $ref: "cardholdersApi.yaml#/x-common-blocks/server" + notes: + <<: *NOTES + href: + type: string + description: Reserved for internal use. Its value will change in a future version. + + Item detail: + type: object + description: | + A call to `/api/items/{id}` (added in 8.40) returns one of these. It adds the item's division + to the summary object. + allOf: + - type: object + properties: + division: + type: object + properties: + id: + type: string + example: "2" + href: + type: string + format: url + example: "https://localhost:8904/api/divisions/2" + - $ref: "#/definitions/Item summary" + + Item types: + type: object + description: Calls to `/api/items/types` return this object, an array of item types. + properties: + itemTypes: + description: A list of item types. + type: array + items: + type: object + properties: + id: + type: string + description: The alphanumeric ID of the item type. Use this ID in the `type` filter when requesting items. + example: "1" + name: + type: string + description: | + 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. + example: "Cardholder" + canonicalTypeName: + <<: *CANON + example: "cardholder" + example: + itemTypes: + - id: "1" + name: "Cardholder" + canonicalTypeName: "cardholder" + - id: "2" + name: "Access Group" + canonicalTypeName: "accessgroup" + + Item update: + type: object + description: | + 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. + properties: + updates: + description: | + 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). 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. + + type: array + items: + type: object + properties: + id: + type: string + description: | + The item's ID, from the list you sent in the POST that created this subscription. + statusFlags: + type: array + description: | + 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](rest.html) of this + documentation. + + 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. + statusText: + type: string + description: | + The state of the item in a multi-line string taken from the server's language pack. + status: + type: string + description: | + A one-line version of the status, with details removed if necessary to keep it short. + next: + description: | + A link to GET more status updates. Do not wait longer than thirty seconds before using + this link 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" + } } + + Item update subscription: + type: object + description: | + Contains a list of item IDs (short alphanums). Send it in the body of a + [POST](#operation--api-items-updates-post) to create a subscription to status updates to the + items with these IDs. + required: [itemIds] + properties: + itemIds: + type: array + items: + type: string + example: "506" + diff --git a/swagger/indexApi.yaml b/swagger/indexApi.yaml new file mode 100644 index 0000000..1b093b0 --- /dev/null +++ b/swagger/indexApi.yaml @@ -0,0 +1,73 @@ +## +## Copyright Gallagher Group Ltd 2020 All Rights Reserved +## THIS IS PROPRIETARY DOCUMENTATION SOURCE CODE OF +## Gallagher Group Research and Development +## Hamilton, New Zealand +## + +swagger: '2.0' + +info: + version: 1.0.0 + title: "Command Centre REST API" + description: | + Welcome to the developer documentation for the Command Centre REST interface. + + The files linked from here show you how you can manage Command Centre using HTTP requests. Each + file contains an introduction to its particular area of the API, instructions on authenticating + to the server, and tips on how to accomplish common tasks, as well as the usual reference + information for paths and data. + + They do not show how to set up the Command Centre server to answer your REST calls. To do that, + search the Configuration Client's online help for 'REST'. + + You could also look at the sample code shipped with Command Centre, on the install media + alongside this documentation in the 'Utilities/REST API' folder. There are several Visual + Studio projects that compile into .NET applications you could observe making HTTP requests against + your own server (in a development environment, of course). + +x-spectacle-topics: + Documentation suite: + description: | + + The API's reference documentation divides into: + + + + + + + + + + + + + +
Alarms, events, and non-cardholder items + + The alarms and events APIs let you download, monitor, and create + events, and download, monitor, and manage alarms. + + The alarms and events documentation also covers API calls that support divisions and items + as they relate to events, as well as bulk status monitoring. + +
Cardholders and related items + The cardholder parts of the API let you manage your users, their personal data and + credentials (cards), and their links to associated items such as access groups, roles, + operator groups, + competencies, and lockers. + +
Status and overrides + + These functions let you monitor and override the types of Command Centre items that have + their own status, including + access zones, alarm zones, doors, fence zones, inputs, outputs, macros, elevator groups, + interlock groups, and schedules. + + Despite its name this section does not cover [mass-monitoring item status](events.html#status-subscriptions). +
PIV cards + This supplement describes how to work with PIV and PIV-I cards. It is separate from the + main cardholder documentation in the interest of brevity. +
+ diff --git a/swagger/pivApi.yaml b/swagger/pivApi.yaml new file mode 100644 index 0000000..8a2bcf9 --- /dev/null +++ b/swagger/pivApi.yaml @@ -0,0 +1,623 @@ +## +## Copyright Gallagher Group Ltd 2020 All Rights Reserved +## THIS IS PROPRIETARY DOCUMENTATION SOURCE CODE OF +## Gallagher Group Research and Development +## Hamilton, New Zealand +## + +swagger: '2.0' + +info: + version: "8.40.2" + title: "Command Centre REST API: PIV card supplement" + description: | + This document is a supplement to the [Cardholder API documentation](cardholders.html) + ('Cardholder' is the Command Centre term for a user). That documentation describes how to add a + card to an existing cardholder in a + [PATCH](cardholders.html#operation--api-cardholders--id--patch), and how to create a cardholder + plus cards in a [POST](cardholders.html#operation--api-cardholders-post), but in the interests + of brevity its examples do not cover PIV. + + The Schema Definitions section gives you the details of the PIV part of a Command Centre card, + with examples of what you would submit to [create one](#definition-PIV-card-create-example) or + [update one](#definition-PIV-card-update-example), and what Command Centre will send you when + you [view one](#definition-PIV-card-GET). + + The Paths section takes those schema examples and wraps them into a sample + [PATCH](#operation--api-cardholders--id--patch) and [POST](#operation--api-cardholders-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 + 2. 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). + 3. Iterate through the array to find the element with `credentialClass: piv`, and + 4. 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 below](#definition-PIV-card-type). + + ### 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](cardholders.html#operation--api-cardholders-get). + + ### 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. + +schemes: + - https +produces: + - application/json +consumes: + - application/json + +securityDefinitions: + "API key": + $ref: "eventsApi.yaml#/securityDefinitions/API key" +security: + - "API key": [] +x-spectacle-topics: + Documentation suite: + $ref: "indexApi.yaml#/x-spectacle-topics/Documentation suite" + + Forward compatibility (HATEOAS): + $ref: "eventsApi.yaml#/x-spectacle-topics/Forward compatibility (HATEOAS)" + +x-common-blocks-this-is-made-up: + + notes_ignored: &NOTES + type: string + example: "Multi-line text..." + description: | + Because of their potential size, notes are only available by request. Use the 'fields' + parameter: + + `?fields=defaults,notes,...` + + contentSigningCert_ignored: &CSC + type: string + description: | + 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. + example: "MIIE[...]Kltk=" + + cardAuthenticationCert_blah: &CAC + type: string + description: | + 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. + + example: "MIIE[...]e5mE=" + + pivAuthenticationCert_etc: &PAC + type: string + description: | + 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. + example: "MIIE[...]wkrp" + + fingerprints_etc: &FINGERS + type: string + description: | + 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: "N7[...]Shpd=" + +paths: + + /api: + get: + summary: GET /api + description: | + This is the first call your application should make. It contains the URLs of every other + call. The one you need for creating PIV cards is an href in `features.cardTypes.cardTypes`. + + responses: + 200: + description: Success + schema: + properties: + example: + version: 8.10.0.0 + features: + cardTypes: + cardTypes: + href: https://localhost:8904/api/card_types + assign: + href: https://localhost:8904/api/card_types/assign + cardHolders: + cardHolders: + href: https://localhost:8904/api/cardholders + 403: + description: The site does not have a REST licence. + + /api/cardholders: + post: + summary: Create a cardholder + description: | + Creates a new cardholder, including assigned cards. + + Part of what you submit is an array called `cards`, each element of which is a card or + credential you want assigned to your new cardholder. This example shows an array containing + one PIV card. + + See the [PIV card create schema definition](#definition-PIV-card-create-example) for the + PIV-specific fields. The fields that are not specific to PIV and PIV-I cards are fully + documented [in the cardholder API](cardholders.html#operation--api-cardholders-post). One, + `division`, is mandatory, and you must also supply either a first or last name for your new + cardholder. + parameters: + - name: "not rendered" + in: body + required: true + schema: + type: object + properties: + division: + type: object + description: | + Mandatory when creating any cardholder. + example: + href: "https://localhost:8904/api/divisions/2" + firstName: + type: string + example: "There will be more fields like this" + description: | + Optional. See [the cardholder API + documentation](cardholders.html#operation--api-cardholders-post). + cards: + type: array + items: {$ref: '#/definitions/PIV card create example'} + + responses: + 201: + description: "Success." + headers: + location: + type: string + format: url + description: The href of the new cardholder. + + /api/cardholders/{id}: + patch: + summary: Update a cardholder + description: | + Updates an existing cardholder, including adding, removing, and modifying cards. + + parameters: + - name: "not rendered" + description: | + Part of what you submit is an object called `cards`, described below. + + The other fields you can pass in this call, such as personal information, access rights, + and card data such as issue levels, status, and validity to/from dates, are fully + documented in the [main cardholder + API](cardholders.html#operation--api-cardholders--id--patch). This example shows how to + assign a PIV card to a cardholder, and how to change the two status fields of an + existing PIV card. + + in: body + required: true + schema: + type: object + properties: + "firstName": + type: string + example: "Algernon" + description: | + See [the cardholder API + documentation](cardholders.html#operation--api-cardholders--id--patch). There is + a lot you can do here. + cards: + type: object + description: | + `cards` can contain three arrays called `add`, `update`, and `delete`. + + An element in the `add` array is the same as you would supply to a + [POST](#operation--api-cardholders-post) when creating a cardholder with cards. + There is an example here. See the [PIV card update schema + definition](#definition-PIV-card-update-example) for the PIV-specific field + definitions. + + An element in the `update` array is much smaller, because the only PIV data you + can change is the PIV status. This example changes the PIV status of a card card + to 'notChecked', which would normally activate it, but in this example we also + disable the card. Presumably we do not want this person using the card just yet. + + Deleting a PIV card is no different from [deleting any other kind of + credential](cardholders.html#operation--api-cardholders--id--cards--card_id--delete). + properties: + add: + type: array + items: {$ref: '#/definitions/PIV card create example'} + + update: + type: array + items: {$ref: '#/definitions/PIV card update example'} + + responses: + 204: + description: "Success." + + get: + summary: Get details of a cardholder + description: | + 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. + + The [PIV card GET schema](#definition-PIV-card-GET) is below. The [main + documentation](cardholders.html#operation--api-cardholders--id--get) covers all the other + fields Command Centre holds for a cardholder. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the cardholder. + + responses: + 200: + description: "Success." + schema: + properties: + "many other cardholder fields": + type: string + example: "etc" + cards: + type: array + items: {$ref: '#/definitions/PIV card GET'} + 4xx: { description: "The operator does not have a privilege that allows reading cardholders, or there is no cardholder at that address." } + +definitions: + PIV card GET: + description: | + In addition to the generic card data the API always returns in the cards section (in the + [Cardholders API document](cardholders.html#definition-Cardholder-card)) of a cardholder + detail page, including `href`, `number`, `issueLevel`, `type`, and `status`, PIV cards have a + `pivData` structure. + + In version 8.00 and later you can check for the value `piv` on `credentialClass`. + + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/cardholders/325/cards/6284082f7ba5eb1" + description: | + This is the same as the href to a card of any other type: you can send an HTTP DELETE to + it to delete the card, but that is the only verb that will work. GET will return a 404. + + Do not specify it when creating a card, since it is the creation process that generates + its href. + number: + type: string + example: "3165-4313-245789-098765432113456799" + description: | + 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. + issueLevel: + type: integer + example: 1 + description: | + A PIV card's issue level is derived from data in the CHUID block, presented here for + convenience. + + status: + description: | + PIV cards have more inactive card states than other cards. 'Time Invalid (PIV + certificate)' or 'Not Trusted', for example. If `status.type` is 'inactive', + `status.value` will give an indication why. + + The status of a new card will be 'revoked' if you set the PIV status (which is different + from the card status, note) to anything but 'normal' or 'offline'. + example: {value: "Not Trusted", type: inactive} + pivData: + description: | + This is much like the [pivData](#definition-PIV-card-data) you send when creating a card, + described later, except that: + + - in certain cases (described below) it does not contain the certificates and + biometrics, leaving just the [CHUID](#definition-CHUID) block (a subset of the card's + CHUID), and the PIV status. + + - it adds `lastCheckTime`, which shows when the card was last validated. That is most + useful when Command Centre is performing periodic card validation. If you are doing + your own validation, this field will hold the moment you last updated the card. + + If the site is running 7.90 and the RESTPIVCardExport customisation, or if it is running a + later version and you asked for extra data using the `fields` query parameter, the pivData + section will contain three certificates and the fingerprint templates in the correct + format for import into another installation of Command Centre. + + The certificates and biometrics are missing from the default result set in the interest of + efficiency: those fields are very large, and most of our integrations do not require + them. Some are running over cellular networks, where bandwidth is carries a cost. + Omitting the large fields by default but sending them on request is a flexible compromise + that allows integrators to build a suitable system without wastage. + + properties: + chuid: + $ref: '#/definitions/CHUID' + pivStatus: + description: | + The `type` field in this block contains the PIV-specific status of the card, as + opposed to the generic credential state which is in a block called `status` at the + same level as `pivData`. + + The [schema for PIV card data](#definition-PIV-card-data) lists the values it might + come back with. + + example: + type: Normal + lastCheckTime: {type: string, format: date-time, example: "2018-04-26T00:22:05Z"} + contentSigningCert: + <<: *CSC + cardAuthenticationCert: + <<: *CAC + pivAuthenticationCert: + <<: *PAC + fingerprints: + <<: *FINGERS + + PIV card type: + description: | + When you [collect card types](cardholders.html#operation--api-card_types-assign-get) from + Command Centre in preparation for giving a PIV card to someone, the card type you seek will + look like this. + properties: + href: + type: string + format: url + description: The number in this URL may be different on your system. + example: "https://localhost:8904/api/card_types/243" + name: + type: string + example: "PIV Card" + availableCardStates: + type: array + items: {type: string, enum: ['Active', 'Disabled (manually)']} + description: | + All credential types have a set of card states. The set assigned to PIV cards contains + only two. + + If you need this, ask for it using the `fields` parameter. + example: + - "Active" + - "Disabled (manually)" + credentialClass: + type: string + enum: [ "piv", "card", "mobile"] + example: piv + description: PIV card types have the credential class 'piv'. + + PIV card create example: + description: | + This example, when placed inside the `cards` array of a + [POST](cardholders.html#operation--api-cardholders-post) or the `cards.add` array of a + [PATCH](cardholders.html#operation--api-cardholders--id--patch), would create a PIV + credential and assign it to the cardholder created by the POST or identified by the address of + the PATCH. Consult those methods for the JSON you need to wrap around this example. + + A PIV-I example would have a different style of FASC-N, and a card number to match. + properties: + number: + example: "47000256001337111234567890199991" + type: string + description: | + Required. + + For a PIV card, this is the FASC-N. + + For a PIV-I card, this is the decimal representation of the GUID in the CHUID, and must + not be the same as the FASC-N. + + The server ignores hyphens here, so you can send back the same number you received from a + GET. + status: + description: | + Optional, but when controlling physical access you should be explicit rather than relying + on a default. Set the `value` field inside it to either 'active' or 'disabled + (manually)'. Case insensitive. + + You will not be able to activate a card if its PIV status (inside the `pivdata` block) + prevents it. + type: object + example: { value: active } + type: + description: | + Required. This should be the href of the built-in PIV/PIV-I card type, that your + application found using the process [in the introduction](#finding-the-piv-card-type). + example: {href: "https://localhost:8904/api/card_types/244"} + pivData: + description: | + This block contains all the PIV-specific fields. Everything outside this block, including + the number, status, and type, is common to all types of Command Centre cards and credentials. + $ref: '#/definitions/PIV card data' + + PIV card update example: + description: | + This example, when placed inside `cards.update` array of a PATCH, would update a card. The + main cardholder API documentation shows where to send the PATCH and what else you can change + with it. This example only shows how to update the status (common to all credential types) + and the PIV data. + + Specifically, the PIV status, because that 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-PIV-card-data) for when you should set the PIV status + and what you can set it to. + + properties: + href: + type: string + format: url + example: https://localhost:8904/api/cardholders/5398/cards/90e5d0d70 + description: | + This is the href of the card you want to update, found in the `cards` array in the + [cardholder detail](cardholders.html#definition-Cardholder-detail). + status: + description: | + Optional. If you omit both this and the PIV status, 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 the card to remain disabled. In that case, set `status.value` to + 'disabled (manually)'. + type: object + example: { value: "disabled (manually)" } + pivData: + description: | + This can contain only one field when you are updating a PIV card, `pivStatus.type`. + + The [PIV card data schema](#definition-PIV-card-data) lists the values you can send. + type: object + example: {pivStatus: {type: "notChecked"}} + + PIV card data: + description: | + This example, when placed inside one of the cards in the `cards` array of a POST or the + `cards.add` array of a PATCH, would create a PIV card. It contains all the PIV-specific + fields on a credential. + + It is the same as the block you [receive from the API](#definition-PIV-card-GET) for an + existing PIV card, minus `lastCheckTime`. + + properties: + chuid: + description: | + Required. 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. + $ref: '#/definitions/CHUID' + pivStatus: + type: object + + description: | + This PIV status is distinct from the 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 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 only if you are doing your own certificate validation. It must + contain a field `type` 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. + + example: {type: Normal} + contentSigningCert: + <<: *CSC + cardAuthenticationCert: + <<: *CAC + pivAuthenticationCert: + <<: *PAC + fingerprints: + <<: *FINGERS + + CHUID: + description: | + This is an example CHUID block for a PIV card. The only difference between this and a PIV-I + card is the FASC-N. + properties: + hash: + type: string + description: | + 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 in versions up to 8.60. Optional in 8.70 and later. + example: "NSBvmBxA8zXz+dScJoYNLb96YMHEZXGghGirRJxWVhE=" + fascn: + type: string + description: | + The FASC-N identifier. Required. + + 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). + example: "47000256001337111234567890199991" + orgIdentifier: + type: string + description: Optional. + example: "" + duns: + type: string + description: Optional. + example: "" + diff --git a/swagger/restApi.yaml b/swagger/restApi.yaml new file mode 100644 index 0000000..9b6d8fe --- /dev/null +++ b/swagger/restApi.yaml @@ -0,0 +1,5052 @@ +## +## Copyright Gallagher Group Ltd 2020 All Rights Reserved +## THIS IS PROPRIETARY DOCUMENTATION SOURCE CODE OF +## Gallagher Group, Hamilton, New Zealand +## + +swagger: '2.0' +openapi: '3.0' + +schemes: [https] +produces: [application/json] +consumes: [application/json] + +info: + version: 1.0.0 + title: "Command Centre REST API: Non-cardholder items" + + description: | + This document describes how you can use the Command Centre REST API to view and manage + access zones, alarm zones, elevator groups, fence zones, doors, macros, inputs, and outputs. + + It has companion documents describing the [Cardholders](cardholders.html) and [Alarms and + Events](events.html) APIs, and Command Centre's handling of [PIV cards](piv.html). + + It assumes some familiarity with HTTP and REST interfaces. Before you start + developing against this interface, you should also read the [Authentication + section](#topic-Authentication) to learn how to format your queries, and Command Centre's + Configuration Client online help to learn how to ready the server to receive them + (search for 'REST API'). + + Each section contains a list of use cases intended as quick solutions for simple tasks + or a how-to to get you started toward your particular goal. However they also serve + as a good introduction to the API if you step through them using a web browser, + starting at `/api` on port 8904 of your Command Centre server. To do that, you will + need to set up Command Centre, and install a browser plugin on your web client that + lets you set an Authorization HTTP header, and a JSON formatter to prettify the + results documents. Search the Configuration client's online help for 'test REST API' + for guidance. + + ### Licensing + + In versions up to and including 8.50, all of the GET calls described here require the RESTStatus + licence. All the POSTs require RESTOverrides. The two licences do not overlap: to watch an + item and override it, you will need both. + + In 8.60 and later the GETs return a limited field set if you have RESTOverrides but not + RESTStatus - enough to let you find the item you want to override. + + 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. + + #### API changes on the roadmap + + * A future version will sort its search results differently if you do not use a query parameter to + override the default. + + #### Changes in 8.80 + + * Early-adopter support for [Interlock groups](#tag-Interlock-Groups). + + #### Changes in 8.70 + + * The server returns 1000 items by default, instead of 100. + + * 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. + + #### Changes in 8.60 + + * The RESTOverrides licence allows you to find items and their override URLs. + * You can search for items by description. + + #### Changes in 8.50 + + * The server property that turns off [client certificate checking](#authentication) changed. + + * A [schedules API](#tag-Schedules) providing CRUD of the six schedule item types. + + * A [day categories API](#tag-Day-Categories) that returns the day categories you need for those + schedules. + + * A [pulse](#operation--api-outputs--id--pulse-post) override on outputs. + + * A 'connectedController' field on the item types that did not already have it. + + * An [elevator groups API](#tag-Elevator-Groups) providing a view of elevator groups so that you + can [set a cardholder's default floor](cardholders.html#operation--api-cardholders--id--patch). + + #### Changes in 8.30 + + All changes to this document contain the text "8.30". + + * A 'connectedController' field in the [door detail](#definition-Door-detail) that returns the + ID and name of the door's hardware controller (a C6000 controller, not an API controller). + + #### Changes in 8.20 + + All changes to this document contain the text "8.20". + + * An [update_cardholder_location](#operation--api-access_zones-update_cardholder_location-get) + call on the Access Zones API that returns you the list of Access Zones into which your + operator is allowed to move cardholders. + + #### Changes in 8.10 + + All changes to this document contain the text "8.10". + + * An [inputs API](#tag-Inputs) that gives you the state of 'input' hardware items and lets you + override them. It is very similar to the outputs API. + + ### Efficiency tips + + * When downloading more than one page of items, sort by ID. + + * Set your page size as large as your client can handle. The default was 100 before 8.70; using + the `top` parameter to take it to 1000 or more gives _much_ better throughput. + + * (In v8.00 or later) avoid the details page by adding detail fields to the summary page with + the [`fields` parameter](#topic-Field-specifiers). One summary page of 1,000 items, while + huge, is much quicker to retrieve than 1,000 detail pages. + + For example, to get the names, doors, and override commands of all your access zones: + + `GET /api/access_zones?fields=name,doors,commands` + + * (In v8.00 or later) save work generating the details page by using the [`fields` + parameter](#topic-Field-specifiers) to reduce the amount of data on each page. Be explicit: + do not use `fields=defaults`, unless you need every field that 'defaults' gives you. + + * When searching for an item by name, if you have its exact name, put it in quotes `"..."`. + Otherwise Command Centre will perform a substring search, which is slower and may return more + results than you need. Even with quotes, the search is case-insensitive. + +x-spectacle-topics: + + Documentation suite: + $ref: "indexApi.yaml#/x-spectacle-topics/Documentation suite" + + Forward compatibility (HATEOAS): + $ref: "eventsApi.yaml#/x-spectacle-topics/Forward compatibility (HATEOAS)" + + Operator privileges: + $ref: "eventsApi.yaml#/x-spectacle-topics/Operator privileges" + + Field specifiers: + $ref: "cardholdersApi.yaml#/x-spectacle-topics/Field specifiers" + + Item status: + description: | + + 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](events.html#status-subscriptions), 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](events.html#status-subscriptions) 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 + 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 is not fully + configured yet. An access zone is not configured until it has at least one door, for + example. + + - `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. + +###################################################################### +securityDefinitions: + "API key": + $ref: "eventsApi.yaml#/securityDefinitions/API key" +security: + - "API key": [] + +x-common-blocks-this-is-made-up: + id_ignored: &ID + type: string + description: | + An alphanumeric identifier, unique to the server. This is the ID to use in the `source` + parameter of [event filters](events.html#operation--api-events-get) and in the body of [status + subscriptions](events.html#status-subscriptions). + + updates_ignored: &UPDATES + type: object + properties: + href: + type: string + format: url + description: | + 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. + + notes_ignored: &NOTES + type: string + example: "Multi-line text..." + description: | + Because of their potential size, notes are only available by request. Use the 'fields' + parameter: + + `?fields=defaults,notes,...` + + sn_ignored: &SHORTNAME + type: string + maxLength: 16 + example: "Short text" + description: | + Short names are not displayed by default. You must ask for them using the + 'fields' parameter: + + `?fields=shortname,...`. + + fds_ignored: &FIELDSDESC_SUM + description: | + Return 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. + + fds_ignored2: &FIELDSDESC_SUM_810 + description: | + Return 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. + + fdd_ignored: &FIELDSDESC_DET + description: | + Return 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. + + fdd_ignored2: &FIELDSDESC_DET_810 + description: | + Return 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. + + fdso_ignored: &FIELDSDESC_SUMONLY + description: | + Return 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. + + statusflags_ignore: &STATUSFLAGS + description: | + 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](events.html#status-subscriptions) if you are running 8.30 or + later, otherwise the item's `updates` link. See the [item status](#topic-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. + type: array + items: { type: string, example: "controllerOffline" } + + statustext_: &STATUSTEXT + description: | + 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. + type: string + example: "Controller offline." + status_: &STATUS + description: | + 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. + type: string + example: "Controller offline." + + update_fields_desc_ignored: &UPDFIELDS + in: query + required: false + type: string + description: | + Returns these fields in the update, instead of the default set. Note that removing fields also + saves you from updates to those fields. + + update_fields_ignore: &UPDFIELDSENUM + enum: [ status, statusText, statusFlags ] + + ccontroller_name_irrelevant: &CONNECTEDCONTROLLER + type: object + description: | + 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. + properties: + name: {type: string, example: "Third floor C6000"} + href: + type: string + format: url + example: "https://localhost:8904/api/items/508" + description: | + 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. + id: + <<: *ID + example: "634" + + acz_ignored: &ACZFIELDS + in: query + required: false + type: string + enum: [href, id, name, shortName, description, division, commands, connectedController, doors, zoneCount, statusFlags, statusText, status, notes, updates] + + alz_ignored: &ALZFIELDS + in: query + required: false + type: string + enum: [href, id, name, shortName, description, division, commands, connectedController, statusFlags, status, notes, updates] + + door_ignored: &DOORFIELDS + in: query + required: false + type: string + enum: [href, id, name, shortName, description, division, commands, connectedController, + entryAccessZone, exitAccessZone, statusFlags, statusText, status, notes, updates] + + eg_ignored: &ELEVATORGROUPALLFIELDS + in: query + required: false + type: string + enum: [href, name, shortName, description, division, notes, elevatorSystem, + elevatorGroupNumber, floorAccess, rearAccessEnabled, groundFloorNumber] + + eg_ignored2: &ELEVATORGROUPCARDHOLDERFIELDS + in: query + required: false + type: string + enum: [href, name, description, division, floorAccess] + + fz_ignored: &FZFIELDS + in: query + required: false + type: string + enum: [href, id, name, shortName, description, division, commands, connectedController, voltage, statusFlags, statusText, status, notes, updates] + + interlock_: &INTERLOCKFIELDS + in: query + required: false + type: string + enum: [href, id, name, shortName, description, division, commands, connectedController, statusFlags, statusText, status, notes, updates] + + macro_ignored: &MACROFIELDS + in: query + required: false + type: string + enum: [href, id, name, description, division, commands, notes, updates] + + output_ignored: &OUTPUTFIELDS + in: query + required: false + type: string + enum: [href, id, name, shortName, description, division, commands, connectedController, statusFlags, statusText, status, notes, updates] + + schedule_ignored: &SCHEDULEFIELDS + in: query + required: false + type: string + enum: [href, name, description, division, notes, type, dayCategories] + +################################################################################################### +################################################################################################### +################################################################################################### +definitions: + Access Zone search: + tags: + - "Access Zones" + description: An array of access zone summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of Access Zone summaries. + items: { $ref: '#/definitions/Access Zone summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/access_zones?skip=1000" + + Access Zones as move targets: + tags: + - "Access Zones" + description: | + An array of access zones, and a link to a special 'outside the system' access zone. This is + the list of access zones into which your operator has the privilege to move cardholders. + properties: + results: + type: array + description: | + An array of Access Zone summaries, just as you would receive from the Access Zone search + GET. + items: { $ref: '#/definitions/Access Zone summary' } + outsideOfSystem: + type: object + description: | + This contains the href you should use to move a cardholder out of all Access Zones. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/access_zones/0" + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/access_zones?skip=1000" + + Access Zone summary: + description: | + `/api/access_zones` returns an array of these. It is a subset of what you get from a + access zone's detail page at `/api/access_zones/{id}` (linked as the href in this + object). + properties: + href: + type: string + format: url + description: | + A link to an [access zone detail](#definition-Access-Zone-detail) object for this access + zone. This is Command Centre's identifier for this access zone: use it whenever you need + to specify an access zone in REST operations. + example: "https://localhost:8904/api/access_zones/3280" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. + + This is the ID to use in the `source` parameter of [event + filters](events.html#operation--api-events-get) if you want to limit your events to + particular access zones. + example: "3280" + name: + type: string + example: "Roswell building 2 lobby" + + Access Zone detail: + description: | + [/api/access_zones/{id}](#operation--api-access_zones--id--get) 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. + + allOf: + - type: object + properties: + description: + type: string + example: "Receives all visitors." + division: + type: object + description: The division containing this Access Zone. + example: + href: "https://localhost:8904/api/divisions/2" + doors: + type: array + description: A list containing names of and links to the doors that control entry to this access zone. + items: + type: object + properties: + name: {type: string} + href: {type: string, format: url, description: "This is the link to the [door's detail](#definition-Door-detail) (which contains a return link back to this page)."} + example: + - name: "Front door" + href: "https://localhost:8904/api/doors/332" + - name: "West stairwell lobby door" + href: "https://localhost:8904/api/doors/745" + zoneCount: + type: integer + description: | + 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](#topic-Item-status) + 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). + + example: 365 + notes: + <<: *NOTES + shortName: + <<: *SHORTNAME + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/access_zones/3280/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["secure"] + connectedController: + <<: *CONNECTEDCONTROLLER + commands: + type: object + description: | + 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). + + properties: + free: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/free" + description: | + [POST](#operation--api-access_zones--id--free-post) to this to override the + zone into 'free - no PIN' access mode until the next scheduled change. + type: string + format: url + freeUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/free" + description: | + [POST](#operation--api-access_zones--id--free-post) to this to override the + zone into 'free - no PIN' access mode for a fixed time. + type: string + format: url + freePin: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/free_pin" + description: | + [POST](#operation--api-access_zones--id--free-post) to this to override the + zone into 'free - PIN' access mode until the next scheduled change. + type: string + format: url + freePinUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/free_pin" + description: | + [POST](#operation--api-access_zones--id--free-post) to this to override the + zone into 'free - PIN' access mode for a fixed time. + type: string + format: url + secure: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/secure" + description: | + [POST](#operation--api-access_zones--id--secure-post) to this to override the + zone into 'secure - no PIN' access mode until the next scheduled change. + type: string + format: url + secureUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/secure" + description: | + [POST](#operation--api-access_zones--id--secure-post) to this to override the + zone into 'secure - no PIN' access mode for a fixed time. + type: string + format: url + securePin: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/secure_pin" + description: | + [POST](#operation--api-access_zones--id--secure-post) to this to override the + zone into 'secure - PIN' access mode until the next scheduled change. + type: string + format: url + securePinUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/secure_pin" + description: | + [POST](#operation--api-access_zones--id--secure-post) to this to override the + zone into 'secure - PIN' access mode for a fixed time. + type: string + format: url + codeOnly: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/code_only" + description: | + [POST](#operation--api-access_zones--id--code_only-post) to this to override + the zone into 'code or card - no PIN' access mode until the next scheduled + change. + type: string + format: url + codeOnlyUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/code_only" + description: | + [POST](#operation--api-access_zones--id--code_only-post) to this to override + the zone into 'code or card - no PIN' access mode for a fixed time. + type: string + format: url + codeOnlyPin: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/code_only_pin" + description: | + [POST](#operation--api-access_zones--id--code_only-post) to this to override + the zone into 'code or card - PIN' access mode until the next scheduled + change. + type: string + format: url + codeOnlyPinUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/code_only_pin" + description: | + [POST](#operation--api-access_zones--id--code_only-post) to this to override + the zone into 'code or card - PIN' access mode for a fixed time. + type: string + format: url + dualAuth: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/dual_auth" + description: | + [POST](#operation--api-access_zones--id--dual_auth-post) to this to override + the zone into 'dual auth - no PIN' access mode until the next scheduled + change. + type: string + format: url + dualAuthUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/dual_auth" + description: | + [POST](#operation--api-access_zones--id--dual_auth-post) to this to override + the zone into 'dual auth - no PIN' access mode for a fixed time. + type: string + format: url + dualAuthPin: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/dual_auth_pin" + description: | + [POST](#operation--api-access_zones--id--dual_auth-post) to this to override + the zone into 'dual auth - PIN' access mode until the next scheduled change. + type: string + format: url + dualAuthPinUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/dual_auth_pin" + description: | + [POST](#operation--api-access_zones--id--dual_auth-post) to this to override + the zone into 'dual auth - PIN' access mode for a fixed time. + type: string + format: url + forgiveAntiPassback: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/forgive_anti_passback" + description: | + [POST](#operation--api-access_zones--id--forgive_anti_passback-post) to this + to forgive anti-passback for all cardholders in the zone. + type: string + format: url + setZoneCount: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/set_zone_count" + description: | + [POST](#operation--api-access_zones--id--set_zone_count-post) to this set the + zone's cardholder count. + type: string + format: url + lockDown: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/lock_down" + description: | + [POST](#operation--api-access_zones--id--lock_down-post) to this set the zone + into lockdown mode. + type: string + format: url + cancelLockDown: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/cancel_lock_down" + description: | + [POST](#operation--api-access_zones--id--cancel_lock_down-post) to this cancel + lockdown on the zone, returning it to its scheduled mode. + type: string + format: url + cancel: + type: object + properties: + href: + example: "https://localhost:8904/api/access_zones/333/cancel" + description: | + [POST](#operation--api-access_zones--id--cancel-post) to this cancel an + active override on the zone, returning it to its scheduled mode. This will + not affect a lockdown. + type: string + format: url + + - $ref: "#/definitions/Access Zone summary" + + Access Zone count: + type: object + required: [ zoneCount ] + properties: + zoneCount: + type: integer + description: | + Put this in the body of access zone override POSTs to set the count of cardholders in the + access zone. + example: 100 + +####################################################################### +####################################################################### + Alarm Zone search: + tags: + - "Alarm Zones" + description: An array of alarm zone summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of Alarm Zone summaries. + items: { $ref: '#/definitions/Alarm Zone summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/alarm_zones?skip=1000" + + Alarm Zone summary: + description: | + `/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). + properties: + href: + type: string + format: url + description: | + A link to an [alarm zone detail](#definition-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. + example: "https://localhost:8904/api/alarm_zones/328" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. + + This is the ID to use in the `source` parameter of [event + filters](events.html#operation--api-events-get) if you want to limit your events + to particular alarm zones. + example: "328" + name: + type: string + example: "Roswell building 2 lobby alarms" + + Alarm Zone detail: + description: | + [/api/alarm_zones/{id}](#operation--api-alarm_zones--id--get) returns one of these. + + allOf: + - type: object + properties: + description: + type: string + example: "Lobby, cafeteria, inbound artefacts." + division: + type: object + description: The division containing this Alarm Zone. + example: + href: "https://localhost:8904/api/divisions/2" + shortName: + type: string + maxLength: 16 + example: "R2 lobby" + notes: + <<: *NOTES + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/alarm_zones/328/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["armed"] + connectedController: + <<: *CONNECTEDCONTROLLER + commands: + type: object + description: | + 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). + + properties: + arm: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/arm" + description: | + [POST](#operation--api-alarm_zones--id--arm-post) to this to arm this alarm + zone until the next scheduled change. + type: string + format: url + name: + example: "Armed" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + enum: [Armed, Set] + default: "Armed" + armUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/arm" + description: | + [POST](#operation--api-alarm_zones--id--arm-post) to this to arm this alarm + zone for a fixed time. + type: string + format: url + name: + example: "Armed" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + enum: [Armed, Set] + default: "Armed" + disarm: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/disarm" + description: | + [POST](#operation--api-alarm_zones--id--disarm-post) to this to disarm this + alarm zone until the next scheduled change. + type: string + format: url + name: + example: "Disarmed" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + enum: [Disarmed, Unset] + default: "Disarmed" + disarmUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/disarm" + description: | + [POST](#operation--api-alarm_zones--id--disarm-post) to this to disarm this + alarm zone for a fixed time. + type: string + format: url + name: + example: "Disarmed" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + enum: [Disarmed, Unset] + default: "Disarmed" + user1: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/user1" + description: | + [POST](#operation--api-alarm_zones--id--user1-post) to this to set the alarm + zone's state to user1 until the next scheduled change. + type: string + format: url + name: + example: "User1" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + default: "User1" + user1Until: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/user1" + description: | + [POST](#operation--api-alarm_zones--id--user1-post) to this to set the alarm + zone's state to user1 for a fixed time. + type: string + format: url + name: + example: "User1" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + default: "User1" + user2: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/user2" + description: | + [POST](#operation--api-alarm_zones--id--user2-post) to this to set the alarm + zone's state to user2 until the next scheduled change. + type: string + format: url + name: + example: "User2" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + default: "User2" + user2Until: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/user2" + description: | + [POST](#operation--api-alarm_zones--id--user2-post) to this to set the alarm + zone's state to user2 for a fixed time. + type: string + format: url + name: + example: "User2" + description: | + Because a site can configure different names for different alarm + zone states, they appear here. + type: string + default: "User2" + cancel: + type: object + properties: + href: + example: "https://localhost:8904/api/alarm_zones/328/cancel" + description: | + [POST](#operation--api-alarm_zones--id--cancel-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). + type: string + format: url + + - $ref: "#/definitions/Alarm Zone summary" + +####################################################################### +####################################################################### + Day category search: + description: An array of day categories, and a `next` link for more. + properties: + results: + type: array + description: An array of day categories. + items: { $ref: '#/definitions/Day category' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/day_categories?skip=1000" + + Day category: + description: | + `/api/day_categories` returns an array of these. Each gives you enough about a day category + to identify it and use it in a schedule: its href, name, and (if you ask for them using the + `fields` parameter) notes and description. + properties: + name: + type: string + example: "Default Day Category" + href: + type: string + format: url + description: | + This is the string to use when placing a day category on a schedule. + example: "https://localhost:8904/api/day_categories/3" + description: + type: string + description: | + Not in the default field set. If you want it, you need to ask for it using `fields`. + example: "Factory default" + notes: + type: string + description: | + Also not in the default field set. If you want it, you need to ask for it. + example: "The default calendar puts every day in this." + +####################################################################### +####################################################################### + Door search: + description: An array of door summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of door summaries. + items: { $ref: '#/definitions/Door summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/doors?skip=1000" + + Door summary: + description: | + `/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). + properties: + href: + type: string + format: url + description: | + A link to a [door detail](#definition-Door-detail) object for this door. + example: "https://localhost:8904/api/doors/332" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. This is the ID to use in the + `source` parameter of [event filters](events.html#operation--api-events-get) + when you are interested in events originating at this door. + + example: "332" + name: + type: string + example: "Front door" + + Door detail: + description: | + [/api/doors/{id}](#operation--api-doors--id--get) returns one of these. + + allOf: + - type: object + properties: + description: + type: string + example: "Main lobby doors." + division: + type: object + description: The division containing this door. + example: + href: "https://localhost:8904/api/divisions/2" + entryAccessZone: + type: object + description: | + The name and href of the access zone to which this door allows entry. + properties: + name: {type: string, example: "Roswell building 2 lobby"} + href: + description: | + This is the address of the [access zone's detail](#definition-Access-Zone-detail) + page (which includes a link back here). + type: string + format: url + example: "https://localhost:8904/api/access_zones/3280" + notes: {<<: *NOTES} + shortName: {<<: *SHORTNAME} + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/doors/332/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["secure", "closed", "locked"] + commands: + type: object + description: | + An array of commands, each represented by a block containing an href that accepts a + POST to send an override. The only override you can send to a door is 'open'. + + 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. + + properties: + open: + type: object + properties: + href: + example: "https://localhost:8904/api/doors/332/open" + description: "[POST](#operation--api-doors--id--open-post) to this to open this door." + type: string + format: url + connectedController: + type: object + description: | + 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. + properties: + name: {type: string, example: "Third floor C6000"} + href: + type: string + format: url + example: "https://localhost:8904/api/items/508" + description: | + This is the REST API's identifier for the hardware controller. It is only an + identifier, not a usable URL, because in 8.30 there is no interface for hardware + controllers. GETting the URL will return a 404. + id: + <<: *ID + example: "634" + + - $ref: "#/definitions/Door summary" + +####################################################################### +####################################################################### + Elevator Group summary: + description: | + The [elevator group search](#operation--api-elevator_groups-get) 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 + 'modify passenger details' search or the elevator group's detail page (which is linked as the + href in this object). + properties: + href: + type: string + format: url + description: | + A link to a [elevator group](#definition-Elevator-Group-detail) object for this + elevator group. + example: "https://localhost:8904/api/elevator_groups/635" + name: + type: string + example: "Main building lower floors" + + Elevator Group Floor Access detail: + description: | + The [modify passenger details search](#operation--api-elevator_groups-modify_passenger_details-get) + returns an array of these. It is everything you need to pick a default floor for a + cardholder, but a subset of what you get from a elevator group's detail page (which is linked + as the href in this object). + allOf: + - type: object + properties: + division: + type: object + description: The division containing this elevator group. + example: + id: "2" + href: "https://localhost:8904/api/divisions/2" + floorAccess: + description: | + An array of objects describing the floors in this elevator group. + + This example only has one floor. Expect more from production systems. + type: array + items: + type: object + properties: + floorNumber: + type: integer + minimum: 1 + example: 1 + description: | + This is Command Centre's internal identifier for the floor. Passengers do + not see it. + frontService: + type: boolean + example: true + + description: | + 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. + rearService: + type: boolean + example: true + + description: | + 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. + floorName: + type: string + example: "Level 1" + description: | + This is a friendly name that Command Centre presents to operators as the name of the + floor. + frontAccessZone: + description: | + 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 ths floor. + type: object + properties: + id: + <<: *ID + example: "637" + name: {type: string, example: "Lvl 1 lift lobby"} + href: + type: string + format: url + example: "http://localhost:8904/access_zones/637" + rearAccessZone: + description: | + 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. + type: object + properties: + id: + <<: *ID + example: "638" + name: {type: string, example: "Lvl 1 lift lobby rear"} + href: + type: string + format: url + example: "http://localhost:8904/access_zones/638" + + - $ref: "#/definitions/Elevator Group summary" + + Elevator Group detail: + description: | + [/api/elevator_groups/{id}](#operation--api-elevator-groups--id--get) returns one of these. + + allOf: + - type: object + properties: + description: + type: string + example: "Main building lobby elevator group." + notes: {<<: *NOTES} + shortName: {<<: *SHORTNAME} + elevatorGroupNumber: + type: integer + example: 1 + description: | + The elevator system's internal identifier for this elevator group. + + elevatorSystem: + type: object + properties: + id: {<<: *ID, example: "632"} + rearAccessEnabled: + description: | + True only if the elevator group uses rear doors on its cars to service the other side + of the shaft. + type: boolean + example: true + groundFloorNumber: + type: integer + minimum: 1 + example: 1 + description: | + The identifier of the floor that this elevator group calls 'ground'. + + - $ref: "#/definitions/Elevator Group summary" + +####################################################################### +####################################################################### + Fence Zone search: + tags: + - "Fence Zones" + description: An array of fence zone summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of Fence Zone summaries. + items: { $ref: '#/definitions/Fence Zone summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/fence_zones?skip=1000" + + Fence Zone summary: + description: | + `/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). + properties: + href: + type: string + format: url + description: | + A link to a [fence zone detail](#definition-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. + example: "https://localhost:8904/api/fence_zones/8487" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. + + This is the ID to use in the `source` parameter of [event + filters](events.html#operation--api-events-get) if you want to limit your events + to those coming from particular fence zones. + example: "8487" + name: + type: string + example: "Storage yard" + + Fence Zone detail: + description: | + [/api/fence_zones/{id}](#operation--api-fence_zones--id--get) returns one of these. + + allOf: + - type: object + properties: + description: + type: string + example: "Trailers and pallets." + division: + type: object + description: The division containing this Fence Zone. + example: + href: "https://localhost:8904/api/divisions/2" + voltage: + type: integer + minimum: 0 + description: | + 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](#topic-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,...`. + example: 7300 + notes: {<<: *NOTES} + shortName: {<<: *SHORTNAME} + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/fence_zones/8487/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["on", "highVoltage", "voltageKnown"] + connectedController: + <<: *CONNECTEDCONTROLLER + commands: + type: object + description: | + 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. + properties: + on: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/on" + description: | + [POST](#operation--api-fence_zones--id--on-post) to this to energise this + fence zone. + type: string + format: url + off: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/on" + description: | + [POST](#operation--api-fence_zones--id--off-post) to this to deactivate this + fence zone. + type: string + format: url + shunt: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/shunt" + description: | + [POST](#operation--api-fence_zones--id--shunt-post) to this to shunt this + fence zone. + type: string + format: url + unshunt: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/unshunt" + description: | + [POST](#operation--api-fence_zones--id--unshunt-post) to this to restore + communication with fence zone. + type: string + format: url + highVoltage: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/high_voltage" + description: | + [POST](#operation--api-fence_zones--id--high_voltage-post) to this to set this + fence zone to 'high voltage' mode. + type: string + format: url + lowFeel: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/low_feel" + description: | + [POST](#operation--api-fence_zones--id--low_feel-post) to this to set this + fence zone to 'low feel' mode. + type: string + format: url + cancel: + type: object + properties: + href: + example: "https://localhost:8904/api/fence_zones/8487/cancel" + description: | + [POST](#operation--api-fence_zones--id--cancel-post) to this cancel an + active override on the fence zone. + + type: string + format: url + + - $ref: "#/definitions/Fence Zone summary" + +####################################################################### +####################################################################### + Input search: + tags: + - "Inputs" + description: An array of input summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of input summaries. + items: { $ref: '#/definitions/Input summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/inputs?skip=1000" + + Input summary: + description: | + `/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). + properties: + href: + type: string + format: url + description: | + A link to an [input detail](#definition-Input-detail) object for this input. + example: "https://localhost:8904/api/inputs/9701" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. This is the ID to use in the `source` + parameter of [event filters](events.html#operation--api-events-get) if you are only + interested in events from particular inputs. + example: "9701" + name: + type: string + example: "Studio door open sensor" + + Input detail: + description: | + [/api/inputs/{id}](#operation--api-inputs--id--get) returns one of these. + + allOf: + - type: object + properties: + description: + type: string + example: "Reed switch." + division: + type: object + description: The division containing this input. + example: + href: "https://localhost:8904/api/divisions/2" + shortName: {<<: *SHORTNAME} + notes: {<<: *NOTES} + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/inputs/9701/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["open"] + connectedController: + <<: *CONNECTEDCONTROLLER + commands: + type: object + description: | + 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). + + properties: + shunt: + type: object + properties: + href: + example: "https://localhost:8904/api/inputs/9701/shunt" + description: | + [POST](#operation--api-inputs--id--shunt-post) to this to shunt the input. + type: string + format: url + unshunt: + type: object + properties: + href: + example: "https://localhost:8904/api/inputs/9701/unshunt" + description: | + [POST](#operation--api-inputs--id--unshunt-post) to this to stop shunting the + input. + type: string + format: url + isolate: + type: object + properties: + href: + example: "https://localhost:8904/api/inputs/9701/isolate" + description: | + [POST](#operation--api-inputs--id--isolate-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. + type: string + format: url + deisolate: + type: object + properties: + href: + example: "https://localhost:8904/api/inputs/9701/deisolate" + description: | + [POST](#operation--api-inputs--id--deisolate-post) to this to stop isolating + the input. The link will be missing if the input is shunted. + type: string + format: url + + - $ref: "#/definitions/Input summary" + +####################################################################### +####################################################################### + Interlock Group search: + tags: + - "Interlock Groups" + description: | + **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. + properties: + results: + type: array + description: An array of interlock group summaries. + items: { $ref: '#/definitions/Interlock Group summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/interlock_groups?skip=1000" + + Interlock Group summary: + description: | + **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). + + properties: + href: + type: string + format: url + description: | + A link to an [interlock group detail](#definition-Interlock-Group-detail) object for this + item. + example: "https://localhost:8904/api/interlock_group/122322" + name: + type: string + example: "Excercise yard egress" + + Interlock Group detail: + description: | + **API support for interlocks is still in development and may change in future versions.** + + [/api/interlock_groups/{id}](#operation--api-interlock_groups--id--get) returns one of these. + + Some of the fields will not come from the server unless you request them via the `fields` + query parameter. + + allOf: + - type: object + properties: + id: + <<: *ID + example: "122322" + description: + type: string + example: "Exercise yard egress." + division: + type: object + description: The division containing this interlock group. + example: + href: "https://localhost:8904/api/divisions/2" + shortName: {<<: *SHORTNAME} + notes: {<<: *NOTES} + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/interlock_groups/122322/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["secure"] + statusText: + <<: *STATUSTEXT + example: "All doors in the group are in a Secure state." + status: + <<: *STATUS + example: "All doors in the group are in a Secure state." + connectedController: + <<: *CONNECTEDCONTROLLER + commands: + type: object + description: | + 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). + + properties: + disable: + type: object + properties: + href: + example: "https://localhost:8904/api/interlock_groups/122322/disable" + description: | + [POST](#operation--api-interlock_groups--id--disable-post) to this to disable the + interlock group, allowing the doors to operate independently. + type: string + format: url + enable: + type: object + properties: + href: + example: "https://localhost:8904/api/interlock_groups/122322/enable" + description: | + [POST](#operation--api-interlock_groups--id--enable-post) to re-enable the + interlock group, causing its doors to return to interlock behaviour. + type: string + format: url + + - $ref: "#/definitions/Interlock Group summary" + +####################################################################### +####################################################################### + Macro search: + tags: + - "Macros" + description: An array of macro summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of macro summaries. + items: { $ref: '#/definitions/Macro summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/macros?skip=1000" + + Macro summary: + description: | + `/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). + properties: + href: + type: string + format: url + description: | + A link to a [macro detail](#definition-Macro-detail) object for this macro. + example: "https://localhost:8904/api/macros/8492" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. This is the ID to use in the `source` + parameter of [event filters](events.html#operation--api-events-get) if you are only + interested in events from particular macros (which would be unusual, since macros do not + generate many events). + example: "8492" + name: + type: string + example: "Arm lobby" + + Macro detail: + description: | + [/api/macros/{id}](#operation--api-macros--id--get) 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. + + allOf: + - type: object + properties: + description: + type: string + example: "Arms and secures all lobby zones." + division: + type: object + description: The division containing this Macro. + example: + href: "https://localhost:8904/api/divisions/2" + shortName: {<<: *SHORTNAME} + commands: + type: object + description: >- + 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. + properties: + run: + type: object + properties: + href: + example: "https://localhost:8904/api/macros/8492/run" + description: "[POST](#operation--api-macros--id--run-post) to this to run this macro." + type: string + format: url + + - $ref: "#/definitions/Macro summary" + +####################################################################### +####################################################################### + Output search: + tags: + - "Outputs" + description: An array of output summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of output summaries. + items: { $ref: '#/definitions/Output summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/outputs?skip=1000" + + Output summary: + description: | + `/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). + properties: + href: + type: string + format: url + description: | + A link to an [output detail](#definition-Output-detail) object for this output. + example: "https://localhost:8904/api/outputs/2365" + id: + type: string + description: | + An alphanumeric identifier, unique to the server. This is the ID to use in the `source` + parameter of [event filters](events.html#operation--api-events-get) if you are only + interested in events from particular outputs. + example: "2365" + name: + type: string + example: "Studio door red/green" + + Output detail: + description: | + [/api/outputs/{id}](#operation--api-outputs--id--get) returns one of these. + + allOf: + - type: object + properties: + description: + type: string + example: "Red or green, controlled from sound desk." + division: + type: object + description: The division containing this output. + example: + href: "https://localhost:8904/api/divisions/2" + shortName: {<<: *SHORTNAME} + notes: {<<: *NOTES} + updates: + <<: *UPDATES + example: { href: "https://localhost:8904/api/outputs/2365/updates/0_0_0" } + statusFlags: + <<: *STATUSFLAGS + example: ["open", "overridden"] + connectedController: + <<: *CONNECTEDCONTROLLER + commands: + type: object + description: | + A block of commands, each represented by a block 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). + + properties: + on: + type: object + properties: + href: + example: "https://localhost:8904/api/outputs/2365/on" + description: | + [POST](#operation--api-outputs--id--on-post) to this to close this output. + type: string + format: url + onUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/outputs/2365/on" + description: | + [POST](#operation--api-outputs--id--on-post) to this to close this output for + a fixed time. + type: string + format: url + off: + type: object + properties: + href: + example: "https://localhost:8904/api/outputs/2365/off" + description: | + [POST](#operation--api-outputs--id--off-post) to this to open this output. + type: string + format: url + offUntil: + type: object + properties: + href: + example: "https://localhost:8904/api/outputs/2365/off" + description: | + [POST](#operation--api-outputs--id--off-post) to this to open this output for + a fixed time. + type: string + format: url + pulse: + type: object + properties: + href: + example: "https://localhost:8904/api/outputs/2365/pulse" + description: | + [POST](#operation--api-outputs--id--pulse-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. + type: string + format: url + + - $ref: "#/definitions/Output summary" + +####################################################################### +####################################################################### + Schedule search: + description: An array of schedule summaries, and a `next` link for more. + properties: + results: + type: array + description: An array of schedule summaries. + items: { $ref: '#/definitions/Schedule summary' } + + next: + type: object + description: The link to the next page. Absent if you have retrieved them all. + properties: + href: { type: string, format: url } + example: + href: "https://localhost:8904/api/schedules?skip=1000" + + Schedule summary: + description: | + A [schedule search](#operation--api-schedule--get) 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). + properties: + href: + type: string + format: url + description: | + A link to a [schedule detail](#definition-Schedule-detail). + example: "https://localhost:8904/api/schedules/6" + name: + type: string + example: "Default Access Zone Secure" + + Schedule detail: + description: | + [/api/schedules/{id}](#operation--api-schedules--id--get) returns one of these, and you put + one of these in the body of a POST or PATCH to create or edit a schedule. + + allOf: + - type: object + properties: + description: + type: string + example: "Secure 24/7" + division: + type: object + description: | + The division containing this schedule. It is possible to change a schedule's + division after creating it. + example: + href: "https://localhost:8904/api/divisions/2" + notes: {<<: *NOTES} + type: + description: | + 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. + type: object + properties: + type: + type: string + enum: [ accessZoneSchedule, accessSchedule, alarmZoneSchedule, outputSchedule, notificationSchedule, hVLFSchedule, elevatorKioskControlSchedule ] + example: "accessZoneSchedule" + dayCategories: + type: array + description: | + 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](#definition-Access-Zone-detail): `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. + + example: [ + { "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: + type: array + description: | + 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. + + example: [{"href": "https://localhost:8904/api/items/637", "name": "Access Zone 1"}, + {"href": "https://localhost:8904/api/items/638", "name": "Access Zone 2"}] + - $ref: "#/definitions/Schedule summary" + + Schedule POST and PATCH: + description: | + [/api/schedules](#operation--api-schedules-post) expects one of these in the body of a POST or + a PATCH. + + The PATCH will ignore the `type` field, because a schedule cannot change its type. + + properties: + name: + type: string + example: "Default Access Zone Secure" + description: + type: string + example: "Secure 24/7" + division: + type: object + description: | + The division into which you want to place this schedule. + example: + href: "https://localhost:8904/api/divisions/2" + notes: + type: string + example: "A default schedule" + type: + description: | + This block contains a string field `type` which must 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 version 8.60. + type: object + properties: + type: + type: string + enum: [ accessZoneSchedule, accessSchedule, alarmZoneSchedule, outputSchedule, notificationSchedule, hVLFSchedule, elevatorKioskControlSchedule ] + example: "accessZoneSchedule" + dayCategories: + type: array + description: | + See the description in the [details page](definition-Schedule-detail). + + 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. + + example: [ + { "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" ] } + ] + } + ] + + Override end time: + type: object + properties: + endTime: + type: string + format: date-time + description: | + 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 will be 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. + + example: "2018-07-31T00:00:00Z" + +################################################################################################### +################################################################################################### +################################################################################################### +tags: +- name: "Access Zones" + + description: | + 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. + + ### 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` + [↪](#operation--api-access_zones-get), 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](#definition-Access-Zone-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. + 3. Take the `updates` [↪](#operation--api-access_zones--id--updates-get) 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. + 4. GET it. + 5. Use the flag rules above to interpret the status flags you receive. + 5. Follow the `next` link to stay up to date. + +- name: "Alarm Zones" + description: | + 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. + 3. The end time you set for an override cannot be in the past or more than 24 hours into + the future. + + ### 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` + [↪](#operation--api-alarm_zones-get), 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` + [↪](#operation--api-alarm_zones--id--arm-post) or `disarm` + [↪](#operation--api-alarm_zones--id--disarm-post), using [the + detail](#definition-Alarm-Zone-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. + 3. Follow the `updates` [↪](#operation--api-alarm_zones--id--updates-get) href from that page. + 4. Use the flag rules above to interpret the status flags you receive. + 5. Follow the `next` link to stay up to date. + +- name: "Day Categories" + description: |- + A day category links a calendar to 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. + + 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. Normally you would follow the `next` + link until there isn't one, but day categories are not that numerous so they will most likely + to fit on one page. Especially if you set `top=1000` (which is advised). + +- name: "Doors" + description: |- + 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). + + ### 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` [↪](#operation--api-doors--id--open-post) URL in the `commands` structure + of the [results](#definition-Door-detail). + + #### Finding a door's status + 1. Find the href for the door using the process above, and GET it. + 3. Follow the `updates` href from that page. + 4. Use the flag rules above to interpret the status flags you receive. + 5. Follow the `next` link to stay up to date. + +- name: "Elevator Groups" + description: |- + 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 of elevator + groups. + 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](cardholders.html#operation--api-cardholders--id--patch) coming up. + + #### Setting a cardholder's default floor + + 1. [Find the href](cardholders.html#tag-Cardholders) for the cardholder. + 2. [PATCH](cardholders.html#operation--api-cardholders--id--patch) it with a [request + body](cardholders.html#definition-Cardholder-PATCH-example) containing the hrefs of the + elevator groups and access zones you wish to set as that cardholder's defaults. + +- name: "Fence Zones" + + description: | + These methods give you read access to Fence Zones in the Command Centre database, and + let you 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. + + ### 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 energising a hardware technician. + - `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. + + #### 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` + [↪](#operation--api-fence_zones-get), 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](#definition-Fence-Zone-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. + 3. Take the `updates` [↪](#operation--api-fence_zones--id--updates-get) 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. + 4. 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. + +- name: "Inputs" + description: |- + 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. + + ### 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](#definition-Input-detail). + 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. + 3. Take the `updates` [↪](#operation--api-inputs--id--updates-get) href from that page. + 4. 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. + +- name: "Interlock Groups" + description: |- + 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. + + ### 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](events.html#status-subscriptions) for those items. + + #### Finding the status of an interlock group + 1. Find the href for the interlock using the process above, and GET it. + 3. Take the `updates` href from that page. + 4. 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](#definition-Interlock-Group-detail). + 4. POST to that URL with an empty body. + +- name: "Macros" + description: | + 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. + + ### Overrides + + You cannot use the REST API to change a macro's schedule. You must use the Command + Centre or Configuration client for that. + + ### Use cases + + #### Listing Macros + 1. `GET /api`. + 2. Follow the link at `features.macros.macros.href` + [↪](#operation--api-macros-get), 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. + 4. If your operator is able to run the macro, the results will contain a URL at + `commands.run.href`. [POST](#operation--api-macros--id--run-post) to that to run the macro - + no body required. + +- name: "Outputs" + description: |- + 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'. + + ### 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` [↪](#operation--api-outputs-get), + 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](#definition-Output-detail) 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. + 3. Take the `updates` [↪](#operation--api-outputs--id--updates-get) href from that page. + 4. 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. + +- name: "Schedules" + description: |- + 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 and RESTOverrides give you all schedule types. + +################################################################################################### +################################################################################################### +################################################################################################### +paths: +###################################################################### + + /api/access_zones: + get: + tags: + - "Access Zones" + summary: Search access zones + description: | + 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. + + Take this URL from the 'href' field in the `features.accessZones.accessZones` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *ACZFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Access Zone search'} + 403: + description: | + 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 licence. + + /api/access_zones/update_cardholder_location: + get: + tags: + - "Access Zones" + summary: Search targetable access zones + description: | + 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. + + Take this URL from the 'href' field in the `features.cardholders.updateLocationAccessZones` + section of `/api`. + + Added in 8.20. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *ACZFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Access Zones as move targets'} + 403: + description: The site does not have the RESTCardholders licence. + + /api/access_zones/{id}: + get: + tags: + - "Access Zones" + summary: Get details of an access zone + description: | + This returns the detail of one access zone. + + Follow the 'href' field in an [access zone summary](#definition-Access-Zone-summary) to get + here. + + parameters: + - name: "id" + in: path + required: true + type: string + - name: "fields" + <<: [*FIELDSDESC_DET, *ACZFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Access Zone detail'} + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404accesszone"} + + /api/access_zones/{id}/free: + post: + tags: ["Access Zones"] + summary: Set an access zone to free + description: | + 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. + + The same URL with `_pin` on the end 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 it an end time in the body, the override will remain in place until the + next scheduled change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/secure: + post: + tags: ["Access Zones"] + summary: Set an access zone to secure + description: | + 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. + + The same URL with `_pin` on the end will send an override to the access zone to change its + mode to 'secure - PIN', meaning you _will_ need a PIN on the door and on a terminal. + + If you do not give it an end time in the body, the override will remain in place until the + next scheduled change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/code_only: + post: + tags: ["Access Zones"] + summary: Set an access zone to code or card + description: | + 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. + + The same URL with `_pin` on the end will override the access zone into 'code or card - PIN', + meaning you _will_ need a PIN when you use a card. + + If you do not give it an end time in the body, the override will remain in place until the + next scheduled change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: body_names_not_rendered + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/dual_auth: + post: + tags: ["Access Zones"] + summary: Set an access zone to dual auth + description: | + Sends an override to an access zone to change its mode to 'dual auth - no PIN', meaning you + will need two cardholders to open its doors, and they will not need a PIN. Terminal + functions will require a card but no PIN. + + The same URL with `_pin` on the end will override the zone into 'dual auth - PIN' mode, + meaning each cardholder will need a PIN for access, and terminal functions will also require + a PIN. + + If you do not give it an end time in the body, the override will remain in place until the + next scheduled change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: body_names_not_rendered + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/forgive_anti_passback: + post: + tags: ["Access Zones"] + summary: Forgive antipassback on a zone + description: | + Sends an override to an access zone to forgive anti-passback for all cardholders in the zone. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/lock_down: + post: + tags: ["Access Zones"] + summary: Lock a zone down + description: | + 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. + + It takes no parameters. The lockdown will remain in place until cancelled, or the access + zone receives an override to another mode. + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/cancel_lock_down: + post: + tags: ["Access Zones"] + summary: Cancel a zone lockdown + description: | + Cancels a lockdown, returning it to its scheduled state. It will not cancel any other kind + of override. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/set_zone_count: + post: + tags: ["Access Zones"] + summary: Set a zone count + description: | + Sets the count of cardholders inside a zone. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: body + required: true + description: "The new cardholder count for the zone." + schema: {$ref: "#/definitions/Access Zone count" } + + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/cancel: + post: + tags: ["Access Zones"] + summary: Cancel mode override + description: | + Cancels an override, returning the access zone to its scheduled state. + + 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: + - {in: path, name: id, required: true, description: "The ID of the access zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403accesszoneoverride" } + + /api/access_zones/{id}/updates: + get: + tags: + - "Access Zones" + summary: Monitor an access zone + description: | + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in an access zone [summary](#definition-Access-Zone-summary) or + [details](#definition-Access-Zone-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the access zone. + - name: "fields" + <<: *UPDFIELDS + enum: [ status, statusText, statusFlags, zoneCount ] + + responses: + 200: + description: | + Success. The [introduction](#tag-Access-Zones) describes the three status fields and + the [access zone detail](#definition-Access-Zone-detail) describes `zoneCount`. + schema: + example: { + updates: { + status: Secure., + statusText: Secure., + statusFlags: [ secure ], + zoneCount: 100 + }, + next: { href: https://localhost:8904/api/access_zones/3280/updates/9_1 } + } + 403: + description: | + 8.50 and earlier: the site does not have the RESTStatus licence. + + 8.60 and later: the site has neither the RESTStatus nor RESTOverrides licence. + 404: {$ref: "#responses/404accesszone"} + +###################################################################### + + /api/alarm_zones: + get: + tags: + - "Alarm Zones" + summary: Search alarm zones + description: | + 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. + + Take this URL from the 'href' field in the `features.alarmZones.alarmZones` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *ALZFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Alarm Zone search'} + 403: + description: | + 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 licence. + + /api/alarm_zones/{id}: + get: + tags: + - "Alarm Zones" + summary: Get details of an alarm zone + description: | + This returns the detail of one alarm zone. + + Follow the 'href' field in an [alarm zone summary](#definition-Alarm-Zone-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the alarm zone. + - name: "fields" + <<: [*FIELDSDESC_DET, *ALZFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Alarm Zone detail'} + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404alarmzone"} + + /api/alarm_zones/{id}/arm: + post: + tags: ["Alarm Zones"] + summary: Arm an alarm zone + description: | + 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. + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/disarm: + post: + tags: ["Alarm Zones"] + summary: Disarm an alarm zone + description: | + 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. + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/user1: + post: + tags: ["Alarm Zones"] + summary: Change an alarm zone to user1 + description: | + 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. + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/user2: + post: + tags: ["Alarm Zones"] + summary: Change an alarm zone to user2 + description: | + 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. + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/armHighVoltage: + post: + tags: ["Alarm Zones"] + summary: Arm an alarm zone (high voltage) + description: | + 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. + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/armLowFeel: + post: + tags: ["Alarm Zones"] + summary: Arm an alarm zone (low feel) + description: | + 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. + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/cancel: + post: + tags: ["Alarm Zones"] + summary: Cancel mode override + description: | + Cancels an override, returning the alarm zone to its scheduled state. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the alarm zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403alarmzoneoverride" } + + /api/alarm_zones/{id}/updates: + get: + tags: + - "Alarm Zones" + summary: Monitor an alarm zone + description: | + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in an alarm zone [summary](#definition-Alarm-Zone-summary) or + [details](#definition-Alarm-Zone-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the alarm zone. + - name: "fields" + <<: [*UPDFIELDS, *UPDFIELDSENUM] + + responses: + 200: + description: | + Success. The [introduction](#tag-Alarm-Zones) describes the three status fields. + schema: + example: { + updates: { + status: Disarmed., + statusText: Disarmed., + statusFlags: [ disarmed ] + }, + next: { href: https://localhost:8904/api/alarm_zones/328/updates/9_1 } + } + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404alarmzone"} + +###################################################################### + /api/day_categories: + get: + tags: + - "Day Categories" + summary: Search day categories + description: | + 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. + + Take this URL from the 'href' field in the `features.dayCategories.dayCategories` section of + `/api`. + + Added in 8.50. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - name: "fields" + <<: *FIELDSDESC_SUMONLY + in: query + required: false + type: string + enum: [href, name, description, notes] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Day category search'} + 403: + description: | + The site does not have the RESTCardholders, RESTStatus, or RESTOverrides licence. + +###################################################################### + /api/doors: + get: + tags: + - "Doors" + summary: Search doors + description: | + 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. + + Take this URL from the 'href' field in the `features.doors.doors` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *DOORFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Door search'} + 403: + description: | + 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 licence. + + /api/doors/{id}: + get: + tags: + - "Doors" + summary: Get details of a door + description: | + This returns the detail of one door. + + Follow the 'href' field in a [door summary](#definition-Door-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the door. + - name: "fields" + <<: [*FIELDSDESC_DET, *DOORFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Door detail'} + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404door"} + + /api/doors/{id}/open: + post: + tags: ["Doors"] + summary: Open a door + description: | + Sends an override to unlock a door. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the door.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403dooroverride" } + + /api/doors/{id}/updates: + get: + tags: + - "Doors" + summary: Monitor a door + description: | + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in a door [summary](#definition-Door-summary) or + [details](#definition-Door-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the door. + - name: "fields" + <<: [*UPDFIELDS, *UPDFIELDSENUM] + + responses: + 200: + description: | + Success. See the [introduction](#tag-Doors) for a description of the three status + fields. + schema: + example: { + 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 } + } + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404door"} + +###################################################################### + /api/elevator_groups/modify_passenger_details: + get: + tags: + - "Elevator Groups" + summary: Search assignable elevator groups + description: | + 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. + + Unlike most of the other calls in this document, it requires the RESTCardholders licence. + + 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. + + Take this URL from the `href` field in the `features.cardholders.modifyDefaultFloors` + section of `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *ELEVATORGROUPCARDHOLDERFIELDS] + + responses: + 200: + description: | + Success. An array of [elevator group(#definition-Elevator-Group-Floor-Access-detail) objects and a + `next` link for more. + + See the note in the description about privileges if your result set is empty. + schema: + properties: + results: + type: array + items: {$ref: '#/definitions/Elevator Group Floor Access detail'} + next: + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/elevator_groups/635" + 403: + description: The site does not have the RESTCardholders licence. + + /api/elevator_groups: + get: + tags: + - "Elevator Groups" + summary: Search elevator groups + description: | + 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. + + Take this URL from the `href` field in the `features.elevators.elevatorGroups` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *ELEVATORGROUPALLFIELDS] + + responses: + 200: + description: | + Success. An array of [elevator group](#definition-Elevator-Group) objects + and a `next` link for more. + schema: + properties: + results: + type: array + items: {$ref: '#/definitions/Elevator Group summary'} + next: + type: object + properties: + href: + type: string + format: url + example: "https://localhost:8904/api/elevator_groups?skip=1000" + 403: + description: | + 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 licence. + + /api/elevator groups/{id}: + get: + tags: + - "Elevator Groups" + summary: Get details of a elevator group + description: | + 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: + - name: "id" + in: path + required: true + type: string + description: The ID of the elevator group. + - name: "fields" + <<: [*FIELDSDESC_DET, *ELEVATORGROUPALLFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Elevator Group detail'} + 403: + description: | + 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 licence. + 404: + description: | + 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'. + +###################################################################### + /api/fence_zones: + get: + tags: + - "Fence Zones" + summary: Search fence zones + description: | + 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. + + Take this URL from the 'href' field in the `features.fenceZones.fenceZones` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *FZFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Fence Zone search'} + 403: + description: | + 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 licence. + + /api/fence_zones/{id}: + get: + tags: + - "Fence Zones" + summary: Get details of a fence zone + description: | + This returns the detail of one fence zone. + + Follow the 'href' field in an [fence zone summary](#definition-Fence-Zone-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the fence zone. + - name: "fields" + <<: [*FIELDSDESC_DET, *MACROFIELDS] + + responses: + 200: + schema: {$ref: '#/definitions/Fence Zone detail'} + description: Success. + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404fencezone"} + + /api/fence_zones/{id}/on: + post: + tags: ["Fence Zones"] + summary: Turn on a fence zone + description: | + Sends an override to an alarm zone to turn it on until the next scheduled or + manual change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/off: + post: + tags: ["Fence Zones"] + summary: Turn off a fence zone + description: | + Sends an override to an alarm zone to turn it off until the next scheduled or + manual change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/shunt: + post: + tags: ["Fence Zones"] + summary: Shunt a fence zone + description: | + Sends an override to an alarm zone to shunt it, effectively preventing all + communication with it. + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/unshunt: + post: + tags: ["Fence Zones"] + summary: Unshunt a fence zone + description: | + Sends an override to an alarm zone to unshunt it, re-enabling its communication. + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/high_voltage: + post: + tags: ["Fence Zones"] + summary: Change to high voltage + description: | + Sends an override to an alarm zone to change it to high voltage mode until the + next scheduled or manual change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/low_feel: + post: + tags: ["Fence Zones"] + summary: Change to low feel + description: | + Sends an override to an alarm zone to change it to 'low feel' mode until the + next scheduled or manual change. + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/cancel: + post: + tags: ["Fence Zones"] + summary: Cancel an override + description: | + Cancels an active override. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the fence zone.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403fencezoneoverride" } + + /api/fence_zones/{id}/updates: + get: + tags: + - "Fence Zones" + summary: Monitor a fence zone + description: | + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in a door [summary](#definition-Fence-Zone-summary) or + [details](#definition-Fence-Zone-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the fence zone. + - name: "fields" + <<: *UPDFIELDS + enum: [ status, statusText, statusFlags, voltage ] + + responses: + 200: + description: | + Success. See the [introduction](#tag-Fence-Zones) for a description of the three status + fields and the [detail page](#definition-Fence-Zone-detail) for `voltage`. + schema: + example: { + updates: { + status: "On - HV.", + statusText: "On - HV.", + statusFlags: [ on, highVoltage ], + voltage: 7300 + }, + next: { href: https://localhost:8904/api/fence_zones/556/updates/9_1 } + } + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404fencezone"} + +###################################################################### + /api/inputs: + get: + tags: + - "Inputs" + summary: Search inputs + description: | + 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. + + Take this URL from the 'href' field in the `features.inputs.inputs` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM_810, + *OUTPUTFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Input search'} + 403: + description: | + 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 licence. + + /api/inputs/{id}: + get: + tags: + - "Inputs" + summary: Get details of an input + description: | + This returns the detail of one input. + + Follow the 'href' field in an [input summary](#definition-Input-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the input. + - name: "fields" + <<: [*FIELDSDESC_DET_810, + *OUTPUTFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Input detail'} + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404input"} + + /api/inputs/{id}/shunt: + post: + tags: ["Inputs"] + summary: Shunt an input + description: | + Sends an override to shunt an input, preventing all communication. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the input.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403inputoverride" } + + /api/inputs/{id}/unshunt: + post: + tags: ["Inputs"] + summary: Unshunt an input + description: | + Sends an override to unshunt an input, re-enabling communication. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the input.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403inputoverride" } + + /api/inputs/{id}/isolate: + post: + tags: ["Inputs"] + summary: Isolate an input + description: | + Sends an override to isolate an input. An isolated input will not prevent an alarm zone + from arming. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the input.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403inputoverride" } + + /api/inputs/{id}/deisolate: + post: + tags: ["Inputs"] + summary: De-isolate an input + description: | + Sends an override to end the isolation of an input. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the input.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403inputoverride" } + + /api/inputs/{id}/updates: + get: + tags: + - "Inputs" + summary: Monitor an input + description: | + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in an input [summary](#definition-Input-summary) or + [details](#definition-Input-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the input. + - name: "fields" + <<: [*UPDFIELDS, *UPDFIELDSENUM] + + responses: + 200: + description: | + Success. See the [introduction](#tag-Inputs) for a description of the three status + fields. + schema: + example: { + 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 } + } + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404input"} + +###################################################################### + /api/interlock_groups: + get: + tags: + - "Interlock Groups" + summary: Search interlock groups + description: | + + **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. + + Take this URL from the 'href' field in the `features.interlockGroups.interlockGroups` + section of `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM_810, + *INTERLOCKFIELDS ] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Interlock Group search'} + 403: + description: | + The site has neither the RESTStatus nor the RESTOverrides licence. + + /api/interlock_groups/{id}: + get: + tags: + - "Interlock Groups" + summary: Get details of an interlock group + description: | + **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](#definition-Interlock-Group-summary) + to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the interlock group. + - name: "fields" + <<: [*FIELDSDESC_DET_810, + *INTERLOCKFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Interlock Group detail'} + 403: + description: | + The site has neither the RESTStatus nor the RESTOverrides licence. + 404: {$ref: "#responses/404interlock"} + + /api/interlock_groups/{id}/disable: + post: + tags: ["Interlock Groups"] + summary: Disable an interlock group. + description: | + **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: + - {in: path, name: id, required: true, description: "The ID of the interlock group.", + type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403interlockoverride" } + + /api/interlock_groups/{id}/enable: + post: + tags: ["Interlock Groups"] + summary: Re-enable an interlock group. + description: | + **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: + - {in: path, name: id, required: true, description: "The ID of the interlock group.", + type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403interlockoverride" } + + /api/interlock_groups/{id}/updates: + get: + tags: ["Interlock Groups"] + summary: Monitor an interlock group. + description: | + **Not yet available. API support for interlocks is still in development and may change in + future versions.** + + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in an interlock group + [summary](#definition-Interlock-Group-summary) or + [details](#definition-Interlock-Group-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the interlock group. + - name: "fields" + <<: [*UPDFIELDS, *UPDFIELDSENUM] + + responses: + 200: + description: | + Success. See the [introduction](#tag-Interlock-Groups) for a description of the status + fields. + schema: + example: { + updates: { + statusFlags: [ open ] + }, + next: { href: https://localhost:8904/api/interlock_groups/122322/updates/9_1 } + } + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404interlock"} + +###################################################################### + /api/macros: + get: + tags: + - "Macros" + summary: Search macros + description: | + 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. + + Take this URL from the 'href' field in the `features.macros.macros` section of `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *MACROFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Macro search'} + 403: + description: | + 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 licence. + + /api/macros/{id}: + get: + tags: + - "Macros" + summary: Get details of a macro + description: | + This returns the detail of one macro. + + Follow the 'href' field in an [macro summary](#definition-Macro-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the macro. + - name: "fields" + <<: [*FIELDSDESC_DET, *MACROFIELDS] + + responses: + 200: + description: Success. + 403: + description: | + 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 licence. + schema: {$ref: '#/definitions/Macro detail'} + 404: {$ref: "#responses/404macro"} + + /api/macros/{id}/run: + post: + tags: ["Macros"] + summary: Run a macro + description: | + Sends a run request to a macro. + parameters: + - {in: path, name: id, required: true, description: "The ID of the macro.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403macrooverride" } + +###################################################################### + /api/outputs: + get: + tags: + - "Outputs" + summary: Search outputs + description: | + 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. + + Take this URL from the 'href' field in the `features.outputs.outputs` section of + `/api`. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM, *OUTPUTFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Output search'} + 403: + description: | + 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 licence. + + /api/outputs/{id}: + get: + tags: + - "Outputs" + summary: Get details of an output + description: | + This returns the detail of one output. + + Follow the 'href' field in an [output summary](#definition-Output-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the output. + - name: "fields" + <<: [*FIELDSDESC_DET, *OUTPUTFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Output detail'} + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404output"} + + /api/outputs/{id}/on: + post: + tags: ["Outputs"] + summary: Turn on an output + description: | + 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. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the output.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403outputoverride" } + + /api/outputs/{id}/pulse: + post: + tags: ["Outputs"] + summary: Pulse an output + description: | + 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: + - {in: path, name: id, required: true, description: "The ID of the output.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 400: { description: The output is not configured for pulsing (8.70 and later only). } + 403: { $ref: "#/responses/403outputoverride" } + + /api/outputs/{id}/off: + post: + tags: ["Outputs"] + summary: Turn off an output + description: | + 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. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the output.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + - in: body + name: ignored + description: Optional + schema: { $ref: "#/definitions/Override end time" } + responses: + 204: { description: Success. } + 400: { $ref: "#/responses/400badbody" } + 403: { $ref: "#/responses/403outputoverride" } + + /api/outputs/{id}/cancel: + post: + tags: ["Outputs"] + summary: Cancel an override + description: | + Cancels an override, returning the output to its previous state. + + parameters: + - {in: path, name: id, required: true, description: "The ID of the output.", type: string} + - $ref: "cardholdersApi.yaml#/parameters/requested_by" + responses: + 204: { description: Success. } + 403: { $ref: "#/responses/403outputoverride" } + + /api/outputs/{id}/updates: + get: + tags: + - "Outputs" + summary: Monitor an output + description: | + See the [item status topic](#topic-Item-status) 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](events.html#status-subscriptions). + + Follow the 'updates' field in an output [summary](#definition-Output-summary) or + [details](#definition-Output-detail) pages to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the output. + - name: "fields" + <<: [*UPDFIELDS, *UPDFIELDSENUM] + + responses: + 200: + description: | + Success. See the [introduction](#tag-Outputs) for a description of the three status + fields. + schema: + example: { + updates: { + status: "This Output is Off.", + statusText: "This Output is Off.", + statusFlags: [ open ] + }, + next: { href: https://localhost:8904/api/outputs/2365/updates/9_1 } + } + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404output"} + +###################################################################### + /api/schedules: + get: + tags: + - "Schedules" + summary: Search schedules + description: | + 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. + + Take this URL from the 'href' field in the `features.schedules.schedules` section of + `/api`. + + Added in 8.50. + + parameters: + - $ref: "cardholdersApi.yaml#/parameters/sort" + - $ref: "cardholdersApi.yaml#/parameters/top" + - $ref: "cardholdersApi.yaml#/parameters/name" + - $ref: "cardholdersApi.yaml#/parameters/division" + - $ref: "cardholdersApi.yaml#/parameters/description" + - name: "fields" + <<: [*FIELDSDESC_SUM_810, + *SCHEDULEFIELDS] + + responses: + 200: + description: | + Success. See the note in the description about privileges if your result set is empty. + schema: {$ref: '#/definitions/Schedule summary'} + 403: + description: | + The site does not have the RESTStatus, RESTOverrides, or RESTCardholders licence. + post: + tags: ["Schedules"] + summary: Create a schedule + description: | + 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`. + + Take this URL from the 'href' field in the `features.schedules.schedules` section of + `/api`. + + Added in 8.50. + parameters: + - name: "not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Schedule POST and PATCH'} + description: | + 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. + responses: + 201: + description: "Success." + headers: + location: + type: string + format: url + description: The href of the new schedule. + 400: + description: | + 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. + + 403: + description: | + 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. + + /api/schedules/{id}: + get: + tags: + - "Schedules" + summary: Get details of a schedule + description: | + This returns the detail of one schedule. + + Follow the 'href' field in an [schedule summary](#definition-Schedule-summary) to get here. + + parameters: + - name: "id" + in: path + required: true + type: string + description: The ID of the schedule. + - name: "fields" + <<: [*FIELDSDESC_DET_810, *SCHEDULEFIELDS] + + responses: + 200: + description: Success. + schema: {$ref: '#/definitions/Schedule detail'} + 403: + description: | + 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 licence. + 404: {$ref: "#responses/404schedule"} + + patch: + tags: ["Schedules"] + summary: Modify a schedule + description: | + Modifies a schedule according to the body of the PATCH. + + Added in 8.50. + + parameters: + - name: "not rendered" + in: body + required: true + schema: {$ref: '#/definitions/Schedule POST and PATCH'} + description: | + 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`. + responses: + 200: + description: | + Success. The response body will contain feedback from the server about your PATCH. + 204: + description: "Success." + 400: + description: | + 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. + + 403: + description: | + 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. + 404: {$ref: "#responses/404schedule"} + + delete: + tags: ["Schedules"] + summary: Delete a schedule + description: | + Deletes the schedule identified by the request's URL. + + Added in 8.50. + + responses: + 204: + description: "Success." + 400: + description: | + 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. + 403: + description: | + 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. + 404: {$ref: "#responses/404schedule"} + +###################################################################### +responses: + 400badbody: + description: | + The server could not parse the POST parameters. There could be a syntax error in your JSON. + + 403accesszoneoverride: + description: | + 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'). + 403alarmzoneoverride: + description: | + 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'). + 403fencezoneoverride: + description: | + 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). + 403dooroverride: + description: | + 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'). + 403outputoverride: + description: | + 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). + 403inputoverride: + description: | + 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. + 403interlockoverride: + description: | + 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. + 403macrooverride: + description: | + 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). + + 404accesszone: + description: | + 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'." + 404alarmzone: + description: | + 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'." + 404door: + description: | + 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'." + 404fencezone: + description: | + 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'." + 404input: + description: | + 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'." + 404interlock: + description: | + 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'." + 404macro: + description: | + 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'. + 404output: + description: | + 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'." + 404schedule: + description: | + 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'." + diff --git a/training/.gitignore b/training/.gitignore new file mode 100644 index 0000000..0521d7a --- /dev/null +++ b/training/.gitignore @@ -0,0 +1,7 @@ +# Ignore HTML files people generated locally +*.html + +# The github workflow in ../github/workflows appends !index.html to this file +# so that it ends up in the gh-pages branch. +# Make sure there is always a trailing linefeed, and no CRs. +!index.html diff --git a/training/README.txt b/training/README.txt new file mode 100644 index 0000000..b18afc5 --- /dev/null +++ b/training/README.txt @@ -0,0 +1,81 @@ +REST training document +====================== + +This directory contains an Asciidoc (.adoc) document intended to accompany online training. It +contains some useful reference material, and general good advice, so it remains online for use by +Gallagher staff and development partners. It is not official Gallagher documentation: no claims +are made about its suitability or veracity, or even sobriety. + +Like the rest of the cc-rest-docs repository, the canonical source is +https://github.com/GallagherSecurity/cc-rest-docs . To push changes you will need a Gallagher +github account and the blessing of one of the GallagerSecurity admins. + + +Editing Asciidoc +================ + +You do not need to preview edits to the .adoc file if you are only changing wording. Simply make +your change and push it to github. It will convert it to HTML using asciidoctor and publish it to +github.io. Give it a minute then check your work at +https://gallaghersecurity.github.io/cc-rest-docs/training . + +However if you are making significant changes, or if you are changing any formatting, you must +preview your changes before pushing them to github. Do that using Asciidoctor. + +It's Ruby, so installing it is simply: + +1. `gem install asciidoctor` +2. I also had to gem `install rouge` to get syntax highlighting in the output HTML. + +Now `asciidoctor rest_training.adoc` will generate `rest_training.html` that you can preview in a +browser. + +Do not commit that HTML to github! There is a .gitignore in there to help prevent that. + + +Screenshots +=========== + +Screenshots are in the 'assets' directory. The rest of this section is no longer relevant. + +When I first added the screenshot images I followed instructions at + +https://gist.github.com/joncardasis/e6494afd538a400722545163eb2e1fa5 + +...which meant creating a branch called "assets" with no history (git checkout --orphan), switching +to it, deleting everything, then adding PNGs. The advantage was that when we changed images we +could splash the whole branch and the old ones would eventually get GCd out of the repo, saving +space and bandwidth. + +The trick was to never merge it with another branch. + +However there are not that many images and they will not change often, so I decided it wasn't worth +the compication and simply added all the images to an 'assets' directory in the same branch as the +document. + + + +Editing Markdown (obsolete now that the document is in Asciidoc rather than Markdown) +================ + +Do it any editor you prefer. You do not need a preview because + +1. install python (or just do everything in git bash, which has it already) +2. In an adminstrator shell: + + pip install grip + + Make sure that worked, because you probably needed to be an admin. + +3. cd ....../cc-rest-docs/training +4. grip -b + + The "-b" will open a browser tab on a preview of your document which will update whenever you + save the markdown. + +And you're done. + +HOWEVER: it uses github's Markdown API, which is rate-limited to 60 anonymous requests per hour +(from you). Either give grip your github username and password, which increases the limit out of +sight, or don't save so often. Or try another markdown editor like Typora. + diff --git a/training/assets/chrome_bad_server_cert_1.png b/training/assets/chrome_bad_server_cert_1.png new file mode 100644 index 0000000..3392812 Binary files /dev/null and b/training/assets/chrome_bad_server_cert_1.png differ diff --git a/training/assets/chrome_bad_server_cert_2.png b/training/assets/chrome_bad_server_cert_2.png new file mode 100644 index 0000000..e5b1ff3 Binary files /dev/null and b/training/assets/chrome_bad_server_cert_2.png differ diff --git a/training/assets/chrome_mod_header_setup.png b/training/assets/chrome_mod_header_setup.png new file mode 100644 index 0000000..cbb8422 Binary files /dev/null and b/training/assets/chrome_mod_header_setup.png differ diff --git a/training/assets/op_group_members.png b/training/assets/op_group_members.png new file mode 100644 index 0000000..ab8594f Binary files /dev/null and b/training/assets/op_group_members.png differ diff --git a/training/assets/op_group_privs.png b/training/assets/op_group_privs.png new file mode 100644 index 0000000..b894160 Binary files /dev/null and b/training/assets/op_group_privs.png differ diff --git a/training/assets/pdf_create_1.png b/training/assets/pdf_create_1.png new file mode 100644 index 0000000..039250b Binary files /dev/null and b/training/assets/pdf_create_1.png differ diff --git a/training/assets/pdf_to_club.png b/training/assets/pdf_to_club.png new file mode 100644 index 0000000..6b97303 Binary files /dev/null and b/training/assets/pdf_to_club.png differ diff --git a/training/assets/postman_auth_header.png b/training/assets/postman_auth_header.png new file mode 100644 index 0000000..a5069d1 Binary files /dev/null and b/training/assets/postman_auth_header.png differ diff --git a/training/assets/postman_client_cert.png b/training/assets/postman_client_cert.png new file mode 100644 index 0000000..48d246b Binary files /dev/null and b/training/assets/postman_client_cert.png differ diff --git a/training/assets/postman_content_type.png b/training/assets/postman_content_type.png new file mode 100644 index 0000000..fd076f3 Binary files /dev/null and b/training/assets/postman_content_type.png differ diff --git a/training/assets/postman_create_cardholder_1.png b/training/assets/postman_create_cardholder_1.png new file mode 100644 index 0000000..05904ae Binary files /dev/null and b/training/assets/postman_create_cardholder_1.png differ diff --git a/training/assets/postman_create_cardholder_result.png b/training/assets/postman_create_cardholder_result.png new file mode 100644 index 0000000..00a38ee Binary files /dev/null and b/training/assets/postman_create_cardholder_result.png differ diff --git a/training/assets/postman_patch_cardholder_1.png b/training/assets/postman_patch_cardholder_1.png new file mode 100644 index 0000000..1d49d73 Binary files /dev/null and b/training/assets/postman_patch_cardholder_1.png differ diff --git a/training/assets/postman_server_cert_warning_off_1.png b/training/assets/postman_server_cert_warning_off_1.png new file mode 100644 index 0000000..de9034b Binary files /dev/null and b/training/assets/postman_server_cert_warning_off_1.png differ diff --git a/training/assets/postman_server_cert_warning_off_2.png b/training/assets/postman_server_cert_warning_off_2.png new file mode 100644 index 0000000..7659d8a Binary files /dev/null and b/training/assets/postman_server_cert_warning_off_2.png differ diff --git a/training/assets/rest_client_api_key.png b/training/assets/rest_client_api_key.png new file mode 100644 index 0000000..f950f26 Binary files /dev/null and b/training/assets/rest_client_api_key.png differ diff --git a/training/assets/rest_client_ip_filtering.png b/training/assets/rest_client_ip_filtering.png new file mode 100644 index 0000000..ecdd346 Binary files /dev/null and b/training/assets/rest_client_ip_filtering.png differ diff --git a/training/assets/server_props_turnon.png b/training/assets/server_props_turnon.png new file mode 100644 index 0000000..16b362e Binary files /dev/null and b/training/assets/server_props_turnon.png differ diff --git a/training/auth_flow.dot b/training/auth_flow.dot new file mode 100644 index 0000000..0aa2f02 --- /dev/null +++ b/training/auth_flow.dot @@ -0,0 +1,72 @@ +# Turn this into auth_flow.dot.pdf using Graphviz: +# dot auth_flow.dot -Tpdf -O +digraph +{ + start[label=Start style=filled fillcolor="#04ff04"] + + node [shape=ellipse, style=filled, fillcolor="#ff9090"] + o_noapikey[label="Raise 'invalid API key' alarm,\nreturn 401"] + o_badprint[label="Raise 'incorrect fingerprint' alarm,\nreturn 401"] + o_badip[label="Raise 'bad source IP' alarm,\nreturn 401"] + o_disabled[label="Return 403"] + o_clientquit[label="Their client faults,\nour server logs all it can\n(which isn't much)"] + + node [shape=box, style=""] + op1[label="Client connects, server sends its certificate"] + o_reqclientcert[label="Server requests client certificate.\n(TLS step one)"] + o_clientsendscert[label="Client sends its certificate"] + o_clientreq[label="Client sends API request\nincluding API key"] + o_argcheck[label="Proceed to argument checks\nand execution" style=filled fillcolor="#04ff04"] + + node [shape=diamond] + cond[label="Client accepts server cert?" ordering=out ] + c_apikeycheck[label="Is there a REST Client item\nwith that API key?"] + c_versioncheck [label="What version is the server?" ] + c_clientcertcheck1[label="Is there a fingerprint\non that REST Client item?"] + c_clientcertcheck2[label="Does the server allow clients with no key?\n('Enable REST Clients with no client certificate'\nserver property on)"] + + c_clientcertcheck840[label="Does the server ignore client certs?\n('Require pinned client certificates'\nserver property off)"] + + c_correctcert[label="Does the client certificate match\nthe client item's fingerprint?" shape=diamond] + c_sourceip[label="Client item has IP restrictions,\nand the client does not meet them?" ordering=out ] + c_disabled[label="Client item is disabled\n(8.90 or later)?" ordering=out ] + c_licence[label="Server has licence\nfor requested operation?"] + c_privcheck[label="Operator has privilege\nfor requested operation?"] + + start->op1 + op1->cond + cond->o_reqclientcert [label = "yes"] + cond->o_clientquit [label = "no"] + o_reqclientcert -> o_clientsendscert + o_clientsendscert -> o_clientreq + o_clientreq->c_apikeycheck + + c_apikeycheck->c_versioncheck [label = "yes"] + c_apikeycheck->o_noapikey [label = "no"] + + c_versioncheck -> c_clientcertcheck1 [label = "8.50 or later"] + c_versioncheck -> c_clientcertcheck840 [label = "before 8.50"] + + c_clientcertcheck1->c_clientcertcheck2 [label= "no"] + + c_clientcertcheck840->c_sourceip [label = "yes"] + c_clientcertcheck840->c_correctcert [label = "no"] + + c_clientcertcheck2->c_sourceip [label = "yes"] + c_clientcertcheck2->o_badprint [label = "no"] + + c_clientcertcheck1->c_correctcert [label = "yes"] + c_correctcert->o_badprint [label = "no"] + + c_correctcert->c_sourceip [label = "yes"] + c_sourceip->c_disabled [label = "no"] + c_sourceip->o_badip [label = "yes"] + + c_disabled->c_licence [label = "no"] + c_disabled->o_disabled [label = "yes"] + + c_licence->c_privcheck [label="yes"] + c_licence->o_disabled [label="no"] + c_privcheck->o_disabled [label="no"] + c_privcheck->o_argcheck [label="yes"] +} diff --git a/training/auth_flow.dot.pdf b/training/auth_flow.dot.pdf new file mode 100644 index 0000000..de42ec8 Binary files /dev/null and b/training/auth_flow.dot.pdf differ diff --git a/training/index.html b/training/index.html new file mode 100644 index 0000000..9a3a484 --- /dev/null +++ b/training/index.html @@ -0,0 +1,6403 @@ + + + + + + + +Command Centre REST API training material + + + + + + +
+
+
+
+

WORK IN PROGRESS.

+
+
+

This document is an AsciiDoc port of a document used internally at Gallagher Group to train +developers in the use of Command Centre’s REST API. This version is not yet suitable for +publication, but if you are using the REST API, and like the sound of 60-odd more pages of reading +material, and are tolerant of casual language and a choppy structure meant to help a live speaker +keep the attention of a room of developers, please continue.

+
+
+

Also, please contact Gallagher through your channel partner and talk to our Technical Support +Engineers about your project, because we are eager to help you make your integration the best it +can be.

+
+

Disclaimer

+
+

This document gives certain information about products and/or services +provided by Gallagher Group Limited or its related companies (referred +to as "Gallagher Group").

+
+
+

The information is indicative only and is subject to change without +notice meaning it may be out of date at any given time. Although every +commercially reasonable effort has been taken to ensure the quality and +accuracy of the information, Gallagher Group makes no representation as +to its accuracy or completeness and it should not be relied on as such. +To the extent permitted by law, all express or implied, or other +representations or warranties in relation to the information are +expressly excluded.

+
+
+

Neither Gallagher Group nor any of its directors, employees or other +representatives shall be responsible for any loss that you may incur, +either directly or indirectly, arising from any use or decisions based +on the information provided.

+
+
+

Except where stated otherwise, the information is subject to copyright +owned by Gallagher Group and you may not sell it without permission. +Gallagher Group is the owner of all trademarks reproduced in this +information. All trademarks which are not the property of Gallagher +Group, are acknowledged.

+
+
+

Copyright © Gallagher Group Ltd 2021. All rights reserved.

+
+
+

Gallagher Group Limited
+PO Box 3026
+Hamilton
+New Zealand
++64 (7) 838 9800
+E-Mail: sales.nz@security.gallagher.com
+Website: www.gallagher.com

+
+
+
+
+

Introduction

+
+
+

DRAFT DO NOT DISTRIBUTE.

+
+
+

This document is an introduction to using the REST API in Command +Centre, aimed at those involved in the development of software that will +integrate Command Centre into other solutions. It was written to +accompany an informal education session with a Gallagher trainer.

+
+
+

It covers features first released in 7.80 and expanded in 7.90.

+
+
+

It uses the following styles for guided examples:

+
+
+
Sample REST query and the resulting JSON
+
+
// GET /api (1)
+{
+  "a text field": "string", // including comments
+  "a numeric field": 1234,
+  "a Boolean field": false
+}
+
+
+
+
    +
  1. +

    Strictly speaking, JSON does not contain comments.

    +
  2. +
+
+
+

This style indicates filenames, URLs, and text that benefits from vertical alignment.

+
+
+
+
Fixed-width blocks are client requests that you can copy out for your own work,
+and server responses, pretty-printed a little to make them readable.
+
+
+
+

Exclusions

+
+

This document does not cover special handling of PIV cards. It shows how +to create a generic card and leaves the variations for PIV and PIV-I to +the developer documentation.

+
+
+

Nor does it cover some of the features added after v7.90: access zones, +alarm zones, fence zones, doors, outputs, inputs, PDF definitions, +and macros. Moving cardholder between access zones, operators, visitors, +schedules, and elevator groups. Subscribing to cardholder updates, an +efficient way of monitoring large numbers of items, and PII redactions.

+
+
+
+
+
+

Start here

+
+
+

Do you want to learn the bare minimum about Command Centre (Gallagher’s access control product) to +get you started on an integration? Read Useful background.

+
+
+

Do you want to set up Command Centre and try out its API from a REST client? Read +Training setup.

+
+
+

Do you want to learn something about HTTP queries in general? HTTP requests will help.

+
+
+

Do you need an introduction to how HTTPS uses certificates? +Authentication and encryption certificates is for you.

+
+
+

Do you want to use client-side certificates to authenticate your client? Very wise. +Client-side certificates has what you need.

+
+
+

Are you more concerned about the security of the API? Look in The request process for how the +server authenticates and authorises requests in general, Client-side certificates for how it +can use certificates to authenticate clients, Notes for penetration testers for a mini-FAQ +on pen test findings, and The API gateway for deep technical information on the API gateway in the cloud.

+
+
+
+
+

References

+
+
+

This document refers to API documentation on github and the online help and sample code +on the Command Centre ISO (or DVD, if you have physical media).

+
+
+

Developer API documentation

+
+

https://gallaghersecurity.github.io/ holds the reference API +documentation. That is the primary reference for the REST API, so it +aims to be complete, and you should have it on hand whenever developing +against Command Centre. However the amount of detail can be daunting and +it is not very introductory, which is why this document exists.

+
+
+

That reference documentation is in four sections:

+
+
+
    +
  • +

    cardholders.html describes the cardholder API calls and supporting concepts, such as card types, +access groups, PDFs, roles, and competencies. These functions were new to 7.90. 8.30 added a +cardholder change-tracking API;

    +
  • +
  • +

    piv.html covers the additional fields you supply and see on PIV and PIV-I cards;

    +
  • +
  • +

    events.html covers the alarms and events calls. This is all that was available in 7.80. 8.10 added +the ability to create your own events;

    +
  • +
  • +

    rest.html covers Command Centre items that are not cardholders, alarms, or events: alarm zones, +access zones, fence zones, outputs, doors, and macros arrived in 8.00, and inputs in 8.10. 8.30 +added a way to mass-monitor items. Schedules and elevator groups arrived later still.

    +
  • +
+
+
+

We are always improving the content so it is best read online, but if you need an offline copy you +can download a ZIP from https://github.com/GallagherSecurity/cc-rest-docs. The documentation is no +longer on the Command Centre install media.

+
+
+

If running on Windows, something in the mix of Internet Explorer, +Javascript, and file: URLs on network shares prevents the HTML rendering +properly so if those files look goofy to you, try a different browser or +copy the folder to your local drive. Or read it online.

+
+
+
+

API gateway technical paper

+
+

If you are thinking of using The API gateway this document will get you started, but if you are +after a deeper understanding:

+
+ +
+
+

On the DVD

+
+

The Configuration Client’s Help menu opens a CHM file that you can also +find in the ISO at +Setup\Program Files\Gallagher\Command Centre\Client\Resources\en +or Setup\Program Files\Gallagher\Command Centre\Bin\Resources\en. There +is a PDF version, split into three volumes, on the ISO in the +Documentation folder.

+
+
+

The Command Centre hardening guide, also on the ISO, is required reading +for security-conscious sites. While you may not be able to follow its +leading advice regarding the REST API ("leave it turned off") there is +plenty more in there to be aware of.

+
+
+
+

Sample code

+
+

See Utilities/REST API/REST API Sample Code.zip in the Command Centre +ISO. There is a WPF client in there and a console application in a C# +Visual Studio solution.

+
+
+
+

Abbreviations

+
+
+
AWS
+
+

Amazon’s cloud.

+
+
CC
+
+

Command Centre, Gallagher’s access control product.

+
+
PDF
+
+

Personal Data Field. Not Adobe’s kind. In Gallagher’s defence, these PDFs predate +Adobe’s.

+
+
PII
+
+

Personal Identifiable Information. Any information in Command Centre’s database concerning a +person, including names, PDFs, movement events, group memberships, roles, competencies, etc.

+
+
+
+
+
+
+
+

Training setup

+
+
+

If you wish to try the REST API for yourself, you will require:

+
+
+
    +
  • +

    Command Centre 7.90 or later with a RESTCardholders licence, a RESTEvents licence if you are to +examine events, RESTStatus if you are to look at site items, RESTOverrides if you want to override +them, and RESTCreateEvents if you wish to create events. This document does not cover the last +three.

    +
  • +
  • +

    A host capable of reaching port 8904 on Command Centre via HTTPS, or access to the desktop of the +CC server itself.

    +
  • +
  • +

    (Recommended) the sample REST client application from the Command Centre installation media (8.10 +onward).

    +
  • +
  • +

    (Optional) the Postman installer, or access to it on the internet. Any REST +client will do, but this document shows how to set up Postman.

    +
  • +
  • +

    (Optional) Chrome and access to the internet for two extensions. Chrome can be easier to use than +Postman, in some cases. Again, any web browser will do, but this document shows how to set up +Chrome.

    +
  • +
  • +

    (Optional) wget or curl, two command-line utilities commonly found on non-Windows systems.

    +
  • +
  • +

    The API developer documentation.

    +
  • +
+
+
+
+
+

Useful background

+
+
+

This section contains material you should have aboard before reading on. +Skip it if you are familiar with CC.

+
+
+

Cardholders

+
+

Cardholders are user accounts. Depending on what you give a cardholder +account it can suit different purposes:

+
+
+
    +
  • +

    people with cards and access needs, but no administrative responsibilities. The REST API allows +management of these kinds of cardholders;

    +
  • +
  • +

    administrative people (operators) with all that plus the rights to configure the system and manage its +users. The 8.50 API was the first with features for managing these kinds of cardholders;

    +
  • +
  • +

    system accounts with no person associated and no physical access, but administrative access to the +system. You are about to create one of these.

    +
  • +
+
+
+
+

Operators and operator groups

+
+

Operators are cardholders with benefits. Cardholders become operators through +membership of one or more operator groups. An operator group bestows +privileges on its members, including the ability to log in to the +Command Centre thick clients or run REST queries.

+
+
+

Operator groups have no effect on access control, so they do not appear in this document again +except when creating an operator which puts a cardholder in an +operator group while setting up a REST client. Operator groups came to the API in 8.50.

+
+
+
+

Access groups

+
+

Cardholders can be members of any number of access groups. An access +group can be a member of one other: its parent. Command Centre considers +a member of a group to be a member of all the groups up its parenting +line, as you would expect.

+
+
+

A cardholder must be a member of an access group before he or she can +open a door, so every cardholder that represents a person should have +group memberships. (Footnote: there are exceptions of course. Some +visitors, for example, do not need to open doors, but they exist in CC +so that it can record their location as they move around the site with +an escort opening doors for them.)

+
+
+

A cardholder can have many memberships of the same group. This is useful +because each has its own start and end times. Past memberships fade +away.

+
+
+

Access groups are not operator groups. When this document refers to a +group it means an access group.

+
+
+

A cardholder must be a member of an access group before he or she can +have personal data, next.

+
+
+
+

Personal data fields (PDFs)

+
+

A Personal Data Field adds a custom value to a +cardholder. Each PDF has a type (text, image, numeric, date, telephone +number, email address, …) and optional constraints on the values that it +can hold. For example, text, email, and telephone number types can have +a regular expression attached which a new value must match before +Command Centre will accept it. A date can have a maximum and a minimum. +Text PDFs can have a list of valid values, like an enumeration.

+
+
+

There is more configuration: image PDFs have a type and size, to which +Command Centre will transcode incoming images. Mobile numbers and email +addresses have a flag indicating whether they are suitable to receive +SMS and email notifications. 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 (Footnote: direct or inherited. Unless otherwise noted, all +Command Centre’s access group membership tests treat inherited members +just like direct members).

+
+
+

The REST API allows you to manage a cardholder’s group memberships (so +that he or she has the PDF) as well as see and set PDF values. It does +not let you change the configuration of the PDF itself.

+
+
+

Whenever this file or the API’s reference documentation uses the term 'PDF' it means a personal data +field.

+
+
+
+

Divisions

+
+

Every item in the API—​we will get to items in a moment—​is in a division (footnote: except day +categories. They are divisionless). Divisions are arranged in a tree: each has exactly one parent, +aside from the root division, which has none. An operator group specifies the roots of the division +trees to which it grants privileges.

+
+
+

Therefore an operator with privileges on the root division has those +privileges on all that server’s objects.

+
+
+

Complication: multi-server clusters have one root node (and therefore +one tree of divisions) per server.

+
+
+

If you find that an operator cannot see or modify an item, the questions +you should ask are:

+
+
+

Which division is the item in?

+
+

The Command Centre client shows a cardholder’s division in the +'Cardholder Details' pane of the cardholder viewer. The Configuration +Client shows the division of any item in the 'General' tab of its +property page. The REST API shows it in the 'division' field.

+
+
+ + + + + +
+
Important
+
+The operator’s division and his or her operator groups' divisions in the 'General' tabs +are irrelevant to privileges. The operator group grants privileges on the divisions in the +'Divisions' tab. +
+
+
+

There is a small section on operator privileges below.

+
+
+
+
+

Access zones

+
+

An access zone represents a physical area with Gallagher-controlled doors +on its perimeter. Something like a room.

+
+
+

An access zone can be open or secure. If open, all its doors are unlocked, +but when it is secure the doors are locked and cardholders attempting entry +will be subject to an access check.

+
+
+

Since a real door has a space on each side of it a Command Centre door can +have two zones attached: an entry zone and an exit zone. The only +difference between the two is the event that Command Centre creates when +someone badges from one to the other: 'access granted' or 'exit granted'.

+
+
+

No matter which way around the zones are attached to a door, when a +cardholder moves through it the resulting event calls the the zone into +which they moved the 'entry' zone, and the one they just left the 'exit' +zone.

+
+
+
+

Roles

+
+

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. It makes more sense when you use the example +'supervisor': a person has a supervisor and is a supervisor for many +others. 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 you can change the cardholder’s supervisor, but to change +who the cardholder supervises you need to edit those individuals.

+
+
+
+

Competencies

+
+

Basically, competencies are another condition that a cardholder must +meet to pass an access check at a door.

+
+
+

The REST API lets you manage the links between cardholders and +competencies: create them, delete them, enable/disable them, and set +their expiry dates.

+
+
+

You might like to sit down for this part. +A competency can be disabled, expired, both, or neither. Actions at a +door can depend on whether a competency is disabled, expired, soon to +expire, or all good.

+
+
+

Whether it is enabled is a flag, plain and simple. Whether it is +expired is derived from an expiry timestamp: if it is +in the past, Command Centre considers the competency expired.

+
+
+

A competency can also have an enable date. If that date (timestamp) +passes while the competency is disabled, Command Centre will enable it.

+
+
+

If the competency is not disabled, the 'expires' time is important. If +it is in the past, the cardholder’s competency is expired. If it is not +set, or it is in the future, the cardholder benefits from the +competency.

+
+
+

A cardholder can have only one link to each competency. They differ from access groups in that way.

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Enabled flagEnablement dateExpiry dateStatus

Set

-

Far future

Active

Set

-

Near future

Active (with a warning at the door)

Set

-

Past

Inactive (expired)

Unset

Future

-

Inactive (pending)

Unset

Past

-

Inactive (disabled)

Unset

null

-

Inactive (disabled)

+
+
+

Card types

+
+

A card type carries rules for the data that a card carries, PINs, how to +treat cards around their expiry time, and default values for new cards +of that type. We often use the word "credential" because not all card +types involve a physical card: there are also biometric and mobile card +types.

+
+
+

The REST API provides read access to card types so that you can manage +cardholders’ credentials.

+
+
+

PIV cards have their own developer document, separate from the rest of +the cardholder API.

+
+
+
+

All the above are items

+
+

The API lets you search for items and examine them, but—​other than +cardholders and schedules—​it does not let create, alter, or delete them. +The purpose of the cardholder API is to let you associate items with +cardholders and manage those associations.

+
+
+
+ +
+

Talking about a PDF or a competency can be confusing, because there is a +PDF item and a competency item, and cardholders can have PDFs and +competencies, but the item and the cardholder’s link to the item are +different things.

+
+
+

The items (on the left in the table below) and the connection to a +cardholder (on the right) both appear in the REST API, but the API only +lets you change the things in the right column, the connections. +So let +us make some definitions:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ItemA cardholder’s possession of that item

Access group

Group membership

Competency

Cardholder competency

Role

Relationship
+(the role is the nature of the relationship between two cardholders)

Locker

Locker assignment

PDF

Cardholder PDF
+or
+PDF value

+
+

There is a question of scale. You may have only two competencies, but thousands of cardholders with +those competencies. You may have only one role in the system, but every one of your cardholders +might have a relationship using that role.

+
+
+

For that reason, the API calls that list items such as roles and competencies do not list their +connections to cardholders. The result sets would be too large. Instead, +you see those connections from the cardholder side: when you GET a +cardholder’s details, you will see all the connections that cardholder +has to PDFs, competencies, groups, cards, lockers, and roles.

+
+
+

Access groups and operator groups will show you their cardholder +members, but only if you ask.

+
+
+
+

Events and alarms

+
+

Events record occurrences in the system. They are not items. They have +an ID, a source item, an occurrence time, and links to other related +items. Events are immutable: the events you read from the REST API will +not change.

+
+
+

Alarms are events with extra fields, and some of them are mutable: there +is a free-text notes field that you can edit in the thick clients, a +history, and Booleans recording whether the alarm is acknowledged, +processed, and active.

+
+
+

The alarms interface only shows unprocessed alarms in its search +results. Once an operator processes an alarm, it disappears from alarm +searches. However, the alarm still exists in the database and an alarm +is also an event, so the events interface will return it whether +somebody processed it or not.

+
+
+
+

Operator privileges

+
+

Or just 'privileges' since there is no other kind.

+
+
+

An operator has privileges over a division and all its subdivisions. +When we refer to an operator having a privilege on a cardholder, for +example, we mean that the operator has that privilege on the +cardholder’s division, or one of its ancestor divisions.

+
+
+

In the interests of security, you should give your REST operators +(footnote: all operators) the minimum privileges they require to +achieve their task.

+
+
+

Appendix: Features and licences gives examples of privileges you will need for various tasks.

+
+
+
+

HTTP requests

+
+

An HTTP request has four parts: a verb, an address, a handful of +headers, and a body.

+
+
+

Verbs

+
+

The verbs we will use are GET, POST, PATCH, and DELETE (in upper case by +convention). GET and DELETE are self-explanatory but the other two are +often confused. In this API we use POST to create something new such as +a cardholder, and PATCH to modify something like the end-date on a group +membership.

+
+
+
+

Addresses (URLs)

+
+

The address is the URL that everyone is accustomed to. In a REST API the +address identifies the object you wish to GET, PATCH, or DELETE. When +POSTing, the address identifies the type of thing you wish to create.

+
+
+
+

Headers

+
+

Headers are a list of key/value pairs. We use one called Authorization +(spelled with a Z) to carry client authentication, and one called +Content-Type to be clear that we use JSON.

+
+
+
+

Bodies

+
+

The body of a GET or DELETE request is empty. A POST can also be empty, +but they usually carry some instructions for what you want created. A +PATCH always needs a body that contains instructions for how to modify +the item identified by the address.

+
+
+

If the body is not empty, it must contain JSON (below).

+
+
+

The sample application "CCFT REST Client" opens a console window that +shows you the verb and address of the HTTP queries it is making. It can +also show you the bodies of its queries and the server’s responses.

+
+
+
+
+

HTTP responses: codes, and more headers and bodies.

+
+

An HTTP response has three parts: a numeric response code, more headers, +and a body.

+
+
+

Response codes

+
+

Any response in the 200-299 range means success. GETs return a 200 along +with their results. DELETEs and MODIFYs return 204 ("no content"), +which just means they succeeded and having nothing more to say. Creating +a cardholder or event returns 201 ("created").

+
+
+

A response in the 400-499 range generally means there was something +wrong with the request. One exception is 409: it could mean that your +timing was bad and trying again later may succeed. Along with 403 and 404, 409 +could also mean you have attempted to do something beyond your +privilege. 401 means you did not sent a good Authorization header and +the server does not trust you at all. +The body of the response that comes back from the server will +tell you the problem.

+
+
+

500-level responses mean the server has met with trouble. Waiting for +updates on events or items is an exception: if you ask Command Centre +for updates and none arrives before the timeout, it will return 503. +That is actually a kind of success: it means nothing changed while you +were waiting, so a future version of Command Centre may return a +200-level code in this case.

+
+
+
+

Headers

+
+

The only time Command Centre returns a header of interest is after it +processes a POST to create a cardholder or event. It sets a header +called Location containing the URL of your new object.

+
+
+
+

Bodies

+
+

The body of a GET response contains everything you asked for, in JSON. +The body that comes back from other verbs is empty unless there was a +problem.

+
+
+

Chrome will show you the body. Press F12 and resend the request to make +Chrome show you the response code and the headers as well (along with +lots of other useful information). Postman (a web client we will get to +later) always shows you everything.

+
+
+
+
+

JSON

+
+

…​though familiarity with XML or any programming language should be +enough. With line breaks and indentation and a bit of colour, JSON is +quite readable.

+
+
+

JSON can contain flat fields, objects (structures), and arrays.

+
+
+
+
{
+  "a text field": "string",
+  "a numeric field": 1234,
+  "a Boolean field": false,
+  "an object": {
+    "sub-field1": "foo",
+    "sub-field2": "bar"
+  },
+  "an array": [
+    {
+      "sub-field1": "jingle",
+      "sub-field2": "bells"
+    },
+    {
+      "sub-field1": "foo",
+      "sub-field2": "bar"
+    }
+  ]
+}
+
+
+
+

In that example, the array called an array shows an array containing two +more objects, each of which contains two fields of its own.

+
+
+
+

Authentication and encryption certificates

+
+

Before an API call can succeed the client needs to decide to trust the +server and then the server needs to decide to trust the client. They do +that using certificates.

+
+
+

First a little background. Very simply put, the current algorithms for +secure communication require a pair of keys. Keys are nothing more +than huge numbers. The two in the pair are different from each other, +but mathematically related so that when you encrypt some data using one +key, nobody can decrypt it if they do not have the other key. The key used for +encryption is public because there is no harm in encrypting data. +People toss those keys around like business cards. Its mate, however, +is very, very private, because it is the one that unlocks the secret message.

+
+
+

These keys do more than just encrypt +and decrypt data so we do not use those terms in their names. The business +card one is called the public key and the other is +the private key.

+
+
+

A certificate contains a public key plus metadata: what the key is +meant for, how long it is good for, and some proof that it is authentic, +if there is any. That proof of authenticity takes the form of a +signature from an authority that the internet has agreed to trust, such +as Symantec or Verizon. Client certificates, and some server +certificates, do not have a signature. Or they do, but it is their own +signature, which does not really count because nobody trusts it. Such +certificates are called self-signed. Web browsers cook up their own +self-signed certificates all the time. Web server certificates, on the +other hand, last for months or years.

+
+
+

If an HTTPS client and server connect and establish an encrypted channel +of communication without checking certificates, it will be secret (nobody +will be able to listen in) +but they should not trust each other. The other end could be +fibbing. So they conduct a negotiation to establish each other’s +identity (i.e., they authenticate).

+
+
+

Usually a web client requires proof of authenticity from the server, +since you want to be sure that it really is your bank’s web site you are +looking at and not a fake. If the server does not provide that, the +client shows a warning. When working with the Command Centre API you +will have to work around it in +Chrome and +work around +it in Postman. If you want to install your own server key, the topic +'Changing the Web Services' in the Configuration Client’s online help +shows you how.

+
+
+

Sometimes the server also requires a proof of identity from the client. +This does not happen when using most web sites because (continuing the +bank example) your bank does not care where you are coming from. +It uses your password to authenticate you. But APIs should operate more +securely than web sites so our recommendation is to turn on the feature +that makes the server check your clients' certificates. +Client-side certificates covers client +certificates.

+
+
+ + + + + +
+
Important
+
+Know the difference between the two certificate checks, and that they are completely +independent. If the client drops a connection because it does not trust the server, Command Centre +cannot raise any alarms, because it never received a proper connection. The problem is on the client +and there is nothing you can do to Command Centre to help. But if the server certificate is +acceptable to the client, the server has a chance to check the client certificate. If the server +does not like the client certificate, Command Centre will raise an 'invalid client certificate' +alarm. +
+
+
+
+
+
+

Enable Command Centre’s API

+
+
+

Turn on the web server

+
+

Configuration client → File → Server Properties → Web Services (about 15 down).

+
+
+

Enable the REST API and–-for the moment–-tick the checkbox to the right of the port. This is an old +screenshot: it changed labels in 8.50. Have a good look at the status because if your server has a +problem binding a socket (which just means "listening"), it will show here first.

+
+
+
+Enabling the public API +
+
Figure 1. Enabling the web server
+
+
+

The Configuration Client’s online help covers this in the topic called +'Web Services'.

+
+
+ + + + + +
+
Warning
+
+⚠ Make sure 'Do not require pinned client certificates' is off in production. In 8.50 it +changed its name to 'Enable REST Clients with no client certificate'. It ships turned off: make sure +it stays off on production servers. +
+
+
+

Requiring pre-shared certificates from clients is the best protection +the server has against attackers on its network. If you tick the box to +turn off that check when you first start your development, come back +once your application is connecting successfully and untick it again. +Read Client-side certificates to help get your application connecting again +after doing that.

+
+
+
+

Installing a custom server certificate

+
+

You do not need to install a custom server certificate for experimental development. If you +eventually choose to do it, it all happens under a button that arrived in Command Centre after I +took the screenshot above, labelled 'Manage Certificates'. The Configuration Client’s online help +covers it in detail in a section called 'Replacing the web service certificate' in the 'Changing the +Web Services' topic. You can either import a public/private key pair into Command Centre (which is +simple, secure, and recommended) or use the Windows Certificate Store (which uses Microsoft’s +security instead of Command Centre’s). The summary of the Certificate Store process is: you need to +name your certificate 'Gallagher Command Centre Server' (please take care with the spelling), place +it in the 'Gallagher Applications / Certificates' folder of the Local Computer Certificate Store, +and give Command Centre the rights to use it. The online help lays that out step by step.

+
+
+
+

Create a REST operator

+
+

We will get to the reasons why in Why we need an operator.

+
+
+

Create an operator group and give it the necessary privileges

+
+

You can do this in either the operational client ("Command Centre") or the configuration client.

+
+
+

Give the group the lowest level privileges it needs. For this exercise, +you will need 'Create and Edit Cardholders' and 'Edit Alarms'. 'Modify +Access Control' and 'View Site' could be handy later.

+
+
+
+Adding privs to an operator group +
+
Figure 2. Adding privs to an op group
+
+
+ + + + + +
+
Warning
+
+Not 'Advanced User'. Never 'Advanced User'. +
+
+
+

See this appendix for a table of +privileges an operator needs for common tasks.

+
+
+

One group is enough for experimenting but when it comes to production, +create an operator group for each class of client you have connecting +and give each group different privileges. An operator can be in more +than one operator group; use this flexibility as you need.

+
+
+
+

Create a cardholder and add it to the operator group

+
+

You can do this in either of the clients.

+
+
+
+Adding cardholders to an op group +
+
Figure 3. Adding cardholders to an op group
+
+
+

In production, your operator should have a bare minimum of +capabilities, so do not give it a card, logon, password, or user code. +Do give it plenty of description about what it does, where it connects +from, and who to contact about it, because the people running the +security system will not be the people who run your software +integrations and they will need all the help you can give them when +problems arise.

+
+
+

During development it helps to log in to the Command Centre clients +sometimes, so I give the REST operator a logon, password, and the +'Launch Configuration Client' privilege.

+
+
+

Now that you have an operator, you need to let the REST API use it.

+
+
+
+
+

Create a REST Client item

+
+

…(in the server) and assign an operator.

+
+
+

We call it a 'REST Client' but it is really a mapping from an API key +to an operator. More on this later.

+
+
+

Using the Configuration client, Configure → Services and Workstations +(at the bottom). Right-click menu → New → REST Client.

+
+
+

Set a name, then go to the 'API Key' tab.

+
+
+

Drag your new operator (Manage → Cardholders) into the 'REST Client +Operator' box. That box looks like it can hold more than one: it cannot.

+
+
+

Take a note of the API key. You will need it for your clients (the +sample app, Chrome, or Postman).

+
+
+
+A REST Client item’s API key in the Configuration Client +
+
Figure 4. A REST Client item’s API key in the Configuration Client
+
+
+

IP filtering is a layer of security that makes it that much harder for +an attacker to attack your server.

+
+
+
+A REST Client’s IP filters in the Configuration Client +
+
Figure 5. A REST Client’s IP filters in the Configuration Client
+
+
+

(A space is as good as a comma.)

+
+
+
+

What is an API key?

+
+

Your client sends this to Command Centre with every request. It is the +username and password combined. Take care of it. If someone steals your +API key and you have not taken other precautions (client certificates +and IP filtering) they could masquerade as you.

+
+
+

If something makes an API call without an API key, or with an API key +that Command Centre cannot find on one of the REST Client items, CC will +raise an error 'A REST connection was attempted with an invalid API +key'.

+
+
+

When a client sends it to the server in an HTTP header it prepends +GGL-API-KEY and a space. That string is not part of the key and you +should not use it in any of the places that expect an API key. It is +just there so the HTTP request conforms to an Internet standard.

+
+
+
+
+
+

Try the sample client application

+
+
+

This section needs fleshing out with proper prose, but until that +happens the major points to cover are:

+
+
+

The sample client is the quickest way to make sure CC is working +properly. For Windows users, it is better than a web browser (which +requires plugins and hides error messages) or Postman (which is fiddly +if the server is checking client certificates).

+
+
+

Find the sample client on the installation media under Utilities / REST +API / RESTClient_version. It has been there since 8.00.

+
+
+

It is not a Command Centre management application! It is a library of +sample code for developers, which happens to compile and run. You can +check the status of many items, override most of them, watch and create +events, and create, look up, and move cardholders, but some features +like lockers and car parks are missing.

+
+
+

Watch the multi-coloured console to find out what URLs to use in your +own requests. Later versions include options on the login screen to also +show the JSON that the client is sending and receiving.

+
+
+

The source code for the demo app and a few others is on the installation +media.

+
+
+
+
+

Set up Chrome

+
+
+

If the sample GUI app works and you want to see the data that comes from +Command Centre, a web browser is all you need. If you also want to +create and change items you should skip this section and install +Postman.

+
+
+

There are two extensions you need to install for Chrome to be really +useful. One sends the API key to the server, and the other dresses up +the JSON that it sends back.

+
+
+ + + + + +
+
Note
+
+Since writing this, several browser extensions have appeared that test REST APIs quite +thoroughly. They may provide a better experience. Shop around. +
+
+
+

Install the ModHeader extension

+
+

You need to set a custom header, because that is how we send the API key +and without that Command Centre will give you nothing.

+
+
+

Start by clicking the 'Modify Headers' icon in Chrome. (Footnote: +confusingly, there is also an extension called 'Modify Headers', which +is different from 'ModHeader'. Use either.)

+
+
+

Set a header called Authorization with a value of GGL-API-KEY followed +by a space and the API key you took from the configuration client. Note +in the example below I have two headers ready to go, only one of which +is active. They are too wide for the Modify Headers window (there are +three more characters).

+
+
+

In 7.90, both must be in upper case.

+
+
+ + + + + +
+
Warning
+
+⚠ Set a filter so that the header only goes to your Command Centre +server. Otherwise Facebook will have your API key. +
+
+
+

Use a URL pattern in the filter that all your queries will match but +other web browsing will not. ModHeader now uses regular expressions, so +if you have dots in your hostname you must put backslashes in front, +\..

+
+
+
+Configure Mod Header Chrome extension +
+
Figure 6. Configure Mod Header Chrome extension
+
+
+
+

Install a JSON viewer

+
+

Raw JSON straight from the server contains no whitespace, so it is not +that easy to read. There are a few Chrome extensions that pretty-print +JSON for you. I use 'Awesome JSON Viewer' because it is recent (April +2020) and can collapse and count sub-items. It is rebranding itself +'JSON Viewer Pro', so you might try searching for that. Despite having +'pro' in the name it remains free.

+
+
+
+

Ignore server certificate warnings

+
+

Send Chrome to https://yourhost:yourport/. yourport will be 8904 unless you changed it when +you set up the web server. If your server does not have a certificate +with a trust path to a trusted root certificate, you need to click through the warning below. It +will reappear occasionally. You can turn it off in Chrome but it is not a good idea, since you want +to know when other servers are using self-signed certificates.

+
+
+
+Chrome fretting about a server cert +
+
Figure 7. Chrome fretting about a server cert
+
+
+
+Chrome fretting in more detail +
+
Figure 8. Chrome fretting in more detail
+
+
+
+
+
+

Set up Postman

+
+
+

If you want to do more than look, you need Postman, because Chrome does +not let you POST, PATCH, or DELETE as easily as Postman does.

+
+
+

Postman used to be a Chrome extension but is now a standalone +application. Both work. These screenshots are from the application.

+
+
+

Send the API header with every request

+
+

This is what the Modify Headers extension does in Chrome. It makes +Postman send an Authorization header containing your API key with every +request.

+
+
+

Your requests also need a Content-Type header but you do not need to set +it yourself. Postman will add that after the next step.

+
+
+
+Setting auth header in Postman +
+
Figure 9. Setting auth header in Postman
+
+
+

There is a mistake in that screenshot: the value for the Authorization +header should have GGL-API-KEY and a space before the API key. Later versions of Command Centre +will not work without it.

+
+
+
+

Set the content type to JSON

+
+

Otherwise Command Centre will reject it as invalid.

+
+
+
+Content-type Postman header +
+
Figure 10. Content-type Postman header
+
+
+

You must use application/json, in lower case, nothing more. People +have tried adding a semicolon and charset=utf8, but that just stops +all queries from working.

+
+
+
+

Never mind that your server certificate is self-signed

+
+

In the current version of Postman, the settings are behind the cog in +the top tool bar, not the sliders in the environment toolbar below it.

+
+
+

For older versions of Postman, the settings are behind the open-ended +wrench in the top tool bar, not the cog in the environment toolbar below +it.

+
+
+
+Postman settings menu +
+
Figure 11. Postman settings menu
+
+
+

Pick 'Settings' and turn off SSL certificate verification. Turn off the +other options if you want to keep it looking clean. It makes no +difference to Command Centre.

+
+
+
+Postman SSL cert verification off +
+
Figure 12. Postman SSL cert verification off
+
+
+
+
+
+

First GETs: cardholders

+
+
+

The most basic GET

+
+

Using Chrome, go to https://yourserver:8904/api again. This document +and the developer documentation use the following shorthand for that instruction, which omits +the protocol, host, and port:

+
+
+
+
GET /api
+
+
+
+

Doing that will test everything you have set up so far. If it did not +work, look at the error message in the response body (Chrome will show +it) and the most recent events in Command Centre.

+
+
+

If it did accept your API key, the only thing that can stop you now is a +licensing problem:

+
+
+
+
{
+  "message": "feature not licensed"
+}
+
+
+
+

With a RESTEvents licence you will get more:

+
+
+
+
{
+  "version": "7.90.0.0",
+  "features": {
+    "items": {...},
+    "alarms": {...},
+    "events": {...}
+  }
+}
+
+
+
+

With a RESTCardholdersEvents licence:

+
+
+
+
{
+  "version": "7.90.0.0",
+  "features": {
+    "items": {...},
+    "alarms": {...},
+    "cardholders": {...},
+    "events": {...},
+    "accessGroups": {...},
+    "roles": {...},
+    "lockerBanks": {...},
+    "competencies": {...},
+    "cardTypes": {...}
+  }
+}
+
+
+
+

That is not the exact JSON you will get (that is not even JSON) but hopefully +you get the idea.

+
+
+
+

Cardholder summary

+
+
+
GET /api/cardholders
+
+
+
+

That means you should Chrome or Postman to https://yourhost:yourport/api/cardholders.

+
+
+

Your operator should be there. Try following some of the links. If you +are using Chrome, just click on them.

+
+
+

One of the links called href (probably the first one) will take you to a cardholder’s details +page, covered in the next section.

+
+
+

Next try:

+
+
+
+
GET /api/cardholders?top=1
+
+
+
+

That limits the results to one cardholder. If you don’t have a next +link in the result, it will be because there is only one cardholder in +your system or your operator only has access to one.

+
+
+

Now apply the advice from the efficiency section of the developer +documentation for collecting a lot of cardholders at once:

+
+
+
+
GET /api/cardholders?sort=id&top=10000
+
+
+
+

v8.00 delivered the ability to add all the fields from the details page +to the summary page, using the fields parameter. See the developer +documentation for a proper description, but in short, try adding +fields=fieldname to your request URL (after a ? or & of course) +where fieldname is the name of a field you can see in a details page, +such as cards or accessGroups. For example:

+
+
+
+
GET /api/cardholders?sort=id&top=10000&fields=firstName,lastName,cards
+
+
+
+
+

Hrefs are URLs as well as identifiers

+
+

The fields called href in the cardholder summary are URLs, +and hopefully you have followed one already. Some will return you a page +of data, and some will 404. We call them hrefs rather than URLs +because they are HTML references that, in our case, happen to be HTTPS +URLs.

+
+
+

Hrefs are very important. Each object in Command Centre—​events, +alarms, items, connections between them—​has one that identifies it. +You will be sending many of them in the bodies of your requests.

+
+
+
+

Cardholder detail

+
+

Follow one of the href links on the summary page:

+
+
+
+
GET /api/cardholders/1234
+
+
+
+

1234 will be a different number on your system. The API documentation +uses the syntax /api/cardholders/{id}. Ignore the braces! There are no +braces in our URLs.

+
+
+

That GET shows you everything the REST API can tell you about the +cardholder (footnote: not quite everything. Mobile credentials and PIV +and PIV-I cards have blobs of data that do not come out unless you ask +for them, because they are so large). The developer documentation helps +interpret it.

+
+
+ + + + + +
+
Tip
+
+
+

The differences between a summary page and a detail page are:

+
+
+
    +
  • +

    A summary page is a search. You pass it search filters, an item limit, and sorting and pagination +instructions and it will return any number of items. A detail page will only tell you about one.

    +
  • +
  • +

    A detail page will return more fields than a summary page, by default. However you should tell +both to only return the fields you need.

    +
  • +
+
+
+
+
+

The API documentation makes heavy use of the terms summary and +detail. You see the summary of an item at root URLs such as +/api/cardholders and /api/access_groups, returned in an array of +many items of the same type as the results of a search. You see the +detail of a lone item by following the item’s href.

+
+
+

It worth becoming familiar with the structure of a cardholder in JSON +because the REST API uses it for summary and detail pages, and it +expects very nearly the same structure when you create or modify a +cardholder. They vary in the quantity of fields and their levels in the +document.

+
+
+
+
+
+

First GETs part two: events

+
+
+

List all events

+
+

This returns 1000, starting with the first recorded:

+
+
+
+
GET /api/events
+
+
+
+

If it takes a while, it is because the JSON viewer extension in Chrome +is pretty-printing it.

+
+
+

From there you can following the next link to get another thousand. +When you have extracted all the events out of Command Centre, an +updates link will replace next. The updates URL is a long poll +link: GETting it will block until more events arrive, or the call times +out.

+
+
+

If you are writing a program that will extract all events out of Command +Centre you should set top (described in the API documentation) as high +as you can. Command Centre will cap it at 10,000. You do not gain much +performance after a couple of thousand, but taking it higher reduces the +number of requests.

+
+
+
+
GET /api/events?top=5000
+
+
+
+

It might take a while for the JSON viewer to render all that JSON.

+
+
+
+

List all alarms

+
+

The alarms interface only returns alarms that have not been processed, +i.e., those that are 'current'. After an operator processes an alarm, it +is merely an event with extra fields.

+
+
+
+
GET /api/alarms
+
+
+
+

That will return at most 100 alarms. You can follow the next link to +get more, until you have got them all and an updates link replaces it. +The updates URL is a long poll: GETting it will block until more +alarms occur or the call times out.

+
+
+
+
+
+

Back into the theory

+
+
+

This section is a grab-bag of facts you need before going further.

+
+
+

API controllers

+
+

Controllers are different parts of the REST API. Not to be confused +with the controller hardware Gallagher also produces, API controllers +have the same name as the part of the request URL after the leading +/api. The main ones are alarms, events, and cardholders. items +is there to support searching for events. card_types, competencies, +access_groups, roles, operator_groups, and locker_banks let you +find items to attach to cardholders.

+
+
+

All controllers’ names are plural, and pothole_cased. You can find links +to them all with

+
+
+
+
GET /api
+
+
+
+
+

Why we need an operator

+
+

Everything that happens to a cardholder happens because an operator did +it. The operator could be a person working in one of the thick clients, +or it could be one of the other APIs, but whenever a cardholder changes, +Command Centre must have an operator to pin it on.

+
+
+

Having an operator allows Command Centre to enforce privileges. You +limit what your REST operator can do in case the client has bugs (and +starts DELETEing URLs instead of GETting them) or the API key becomes +known to the other side.

+
+
+

It also helps auditing. Each cardholder change causes an operator event, +with the operator and cardholders as related items. Interactive changes +use the workstation as the source, and REST changes use the REST Client. +You can run reports that filter on the source and operator to monitor +your integration.

+
+
+
+

The request process

+
+

This is shown in a rough flowchart.

+
+
+

All HTTPS requests start like this:

+
+
+
    +
  1. +

    The client and server establish an encrypted channel. Part of that is +a certificate exchange. The channel makes the following conversation +safe from eavesdroppers but does not confirm the identity of either +side.

    +
  2. +
  3. +

    Unless you have configured your client not to, it verifies the +identity of the server (authenticates it) by examining the contents of +the certificate that came from the server during the previous step. If +the client does not like the certificate that came from the server, it +drops the connection. Command Centre will complain to its log file when +this happens but because it did not receive a request, will not create +an event.

    +
  4. +
  5. +

    If the client trusts the server it sends its request along with a +secret that proves it is who it says it is. In our case that is an HTTP +header containing the API key.

    +
  6. +
+
+
+

So far that has been a normal HTTPS conversation, the same as what +happens with every web site you visit in a browser. From here on is +specific to Command Centre.

+
+
+
    +
  1. +

    The server looks for the API key in the Authorization header and +finds the matching REST Client (footnote: capitalised to mean the +configuration item in Command Centre, not the REST client software on +the other end of the TCP connection) in the database. If it cannot find +one, it will raise an alarm "A REST connection was attempted with an +invalid API key".

    +
  2. +
  3. +

    If you did not disable pinned client certificates in the server +properties (Web Services tab), or if you are running 8.50 and the REST +Client item has a thumbprint on it (in the API Key tab), it checks the +thumbprint of the request’s certificate against the one on the REST +Client item. If they do not match, it responds with a 401 and raises an +alarm "A REST connection was attempted with an invalid client +certificate". The server does not check the client certificate’s chain +of trust. Client-side certificates has all the +details of why you would want your server to check client certificates +and how to create them.

    +
  4. +
  5. +

    It checks the source host’s IP number against the REST Client item’s +IP filters. If it does not match, it responds with a 401 and raises an +alarm "A REST connection was refused because of the connecting IP +address does not match the IP filter on the REST Client name of your +REST Client".

    +
  6. +
  7. +

    It checks that it has a license for the controller that will handle +the request. If it does not, it sends a 403 response containing the +string "Feature not licensed".

    +
  8. +
  9. +

    It creates a new session for the operator, if there isn’t one ready, +then compares what the request is asking for against the REST Client’s +operator’s privileges from the session. If the privileges do not allow +the operation that the client requested, the server will respond with a +400-level error and a message in the body.

    +
  10. +
+
+
+

If all those steps succeed, the API controller processes the request, +logs an operator event if something changed, and returns a result.

+
+
+

The alarms above have a default priority of medium-high. The server +raises them for two reasons: while developing, it is useful to have a +little more diagnosis coming out of the server, and in production, it is +good to know when your API is being probed.

+
+
+

If too many bad requests arrive too quickly, the server will assume it +is under attack and will log an alarm at maximum priority, then will +remain silent on the matter until the attack stops.

+
+
+

Errors also go to +%PROGRAMDATA%\Gallagher\Command Centre\Command_centre.log.

+
+
+
+
+
+

First POST and search

+
+
+

Create a cardholder

+
+

In Postman:

+
+
+
+POST to create a cardholder +
+
Figure 13. POST to create a cardholder
+
+
+

Notice that there are two headers set: Authorization contains the API +key and content-type tells the server that the body is JSON.

+
+
+

This document uses this shorthand to represent that kind of HTTP query:

+
+
+
+
// POST /api/cardholders
+{
+  "firstName": "New",
+  "lastName": "Cardholder",
+  "division": {
+    "href":"https://localhost:8904/api/divisions/2"
+  }
+}
+
+
+
+

The first line gives the verb and the file part of URL. It needs the protocol, host, and port added, +to form the final request: a POST to https://yourserver:8904/api/cardholders. The rest is the +body.

+
+
+

When you create a cardholder you must specify the division and either +the first or last name, so this example is about the shortest you can +get away with.

+
+
+

Look at the response from the POST. It contains a Location header +giving the URL of our new cardholder.

+
+
+
+Create cardholder result +
+
Figure 14. The result of creating a cardholder
+
+
+

You could GET that URL to see what you created, or…

+
+
+
+

Search for a cardholder

+
+
+
GET /api/cardholders?name=new
+
+
+
+

That will return all the cardholders with 'new' in their name. It is +case-insensitive.

+
+
+

To be more precise:

+
+
+
+
GET /api/cardholders?name="cardholder, new"
+
+
+
+

Quotes make it a full string match, rather than a substring match. It is +still case-insensitive.

+
+
+

Note how Command Centre matches your search string against a +concatenation of the cardholder’s last name, a comma, a space, and the +first name. It only does that if the cardholder has both names set. +Otherwise it just uses the one.

+
+
+

Also note that Chrome will turn the space into %20.

+
+
+

You should see your new cardholder in the results of both those queries.

+
+
+
+
+
+

Cardholder flat fields

+
+
+

"Flat fields" isn’t a term the REST API uses but it means the simple data like names, description, +and PDF values that sit at the top level of a cardholder and do not have structures of their +own. Other data such as cards, access group memberships, and competency assignments are one level +down, in arrays, and contain other fields.

+
+
+

Setup: give a cardholder access groups and PDFs

+
+

For a cardholder to have a PDF, both need to be on the same access +group. You cannot create PDFs or assign them to access groups via REST +so you must do that in the Configuration Client. Adding cardholders to +groups is possible via REST of course, but that is easier if you have an +existing group membership to compare your efforts against, so for now we +will do that in the client as well.

+
+
+

Create some PDFs

+
+

In the Configuration Client, Configure → Personal Data Fields (second +from the top).

+
+
+

Add → New Personal Data Field. Call it 'email' and set the type (on the +Type tab) to Email.

+
+
+

You might as well make a few more with different data types. Make at +least one text, because they have no constraints and are easiest to +experiment with.

+
+
+
+Create a PDF in Configuration Client +
+
Figure 15. Create a PDF in Configuration Client
+
+
+
+

Create at least two access groups, add the PDFs, and add your cardholder

+
+

In the configuration client, Manage → Access Groups, right-click menu, +New → Access Group.

+
+
+

Open the cardholder and PDF lists out of the Manage menu so that you can +drag items out of them.

+
+
+

Drag your new cardholder to the Cardholder Membership tab of the access +group.

+
+
+

Drag your PDFs to the Personal Data tab of the access group.

+
+
+

Repeat!

+
+
+
+Add a PDF to a group in Config Client +
+
Figure 16. Add a PDF to a group in Config Client
+
+
+

Save everything in the configuration client then reload your cardholder’s details in your REST +client to see what PDF values and group memberships look like in JSON. These sections in the +cardholder API documentation cover it:

+
+
+
    +
  • +

    'Cardholder detail' gives the layout of a cardholder’s detail page.

    +
  • +
  • +

    'Cardholder PDF' describes the items in the personalDataDefinitions array.

    +
  • +
  • +

    'Cardholder access group' describes the items in the accessGroups array.

    +
  • +
+
+
+

Now you can change some of those values.

+
+
+
+
+

Change a name, authorise, change simple PDFs, set user code, etc.

+
+

This example changes a cardholder’s first name and two PDFs, authorises +it (de-authorised cardholders always fail access checks), turns on a +flag that allows extra unlock time on doors, and sets the user code +(which is a number you can use at keypads):

+
+
+
+
// PATCH /api/cardholders/{id}
+{
+  "firstname": "Jeremiah",
+  "@datePDF": "2099-03-31",
+  "@email": "a@b.com",
+  "authorised": true,
+  "useExtendedAccessTime": true,
+  "userCode": "1234"
+}
+
+
+
+

It looks like this in Postman:

+
+
+
+PATCH a cardholder in Postman +
+
Figure 17. PATCH a cardholder in Postman
+
+
+

It looks a lot like that in the cardholder’s details page too, so here +is the rule:

+
+
+ + + + + +
+
Tip
+
+When PATCHing flat fields on a cardholder, send back the same kind of JSON you got from a GET. +
+
+
+
+

Image PDFs and Base64

+
+

Here is a cutting from the details page of a cardholder with an image +PDF:

+
+
+
+
// GET /api/cardholders/{id}
+{
+  ...
+  "@datePDF": "2099-03-31T00:00:00Z",
+  "@Email": "a@b.com",
+  "@Mugshot": {
+    "href": "https://localhost:8904/api/cardholders/325/personal_data/8449"
+  }
+  ...
+}
+
+
+
+

Notice that the image PDF does not show in a cardholder’s details, +because they can be massive. Instead you get a URL. If you follow that +link you will see the image.

+
+
+

In order to send binary data in JSON (which cannot contain non-printable +characters), you have to encode it to Base64. This turns raw bytes into +a string of letters, numbers, plusses, and slashes (64 possible +characters), sometimes with equals signs on the end. It also increases +the size of the data by about a third. You can put the string between +quotes and send it like any other PDF:

+
+
+
+
// PATCH /api/cardholders/{id}
+{
+  "@photo": "Kilobytes+of+Base64+encoded+data==="
+}
+
+
+
+ + + + + +
+
Tip
+
+If you see a load of what looks like garbage ending with equals signs, it is probably Base64. +
+
+
+
+
+
+

Cards

+
+
+

Meaning credentials. In this section you will see how to give a +cardholder a card and modify existing cards.

+
+
+

Adding, updating, and deleting cards

+
+

Like all cardholder modifications, you do this with a PATCH to the +cardholder href. However a card is not a flat field: it is a member of +an array in the cardholder object called cards. To add an item to the +cards array, or change one, you pass in an object also called cards. +True to previous advice, we do all operations in one PATCH.

+
+
+

Borrowing from the API documentation:

+
+
+
+
+

The cards object can contain three arrays, named add, update, and +remove. Every element you put in those arrays should be in the card +schema that you see in a cardholder detail.

+
+
+

Each element of the add array will need a type member, at the very least. The only card field +that does not make sense here is href, because an href in a card block names an existing card to +change, but here you are creating one.

+
+
+

The example below adds two cards: one has nothing more than the type, so it will receive a computed +number and issue level, and blank from and until dates. The other is a mobile credential with a +custom initial state 'Pending sign-off'. You can tell it is a mobile credential because only they +have invitation blocks.

+
+
+

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 (making it valid forever).

+
+
+

The only field that makes sense in an element of the remove array is href.

+
+
+

Do not put the same href in both the update and remove arrays.

+
+
+
+
+

Here is the example. As well as adding two credentials, modifying a third, and removing another, it +authorises the cardholder and sets a PDF called employeeID just to remind you that you can combine +operations:

+
+
+
+
//PATCH /api/cardholders/{id}
+{
+  "authorised": true,
+  "@employeeID": "THX1139",
+  "cards": {
+    "add": [
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/354"
+        }
+      },
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/600"
+        },
+        "number": "Jock's iPhone 8",
+        "status": {
+          "value": "Pending sign-off"
+        },
+        "invitation": {
+          "email": "jock@example.com"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
+        "issueLevel": 2,
+        "until": ""
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/77e8affe7c7e4b56"
+      }
+    ]
+  }
+}
+
+
+
+

Notice how the hrefs of a card include the cardholder’s href and end +with a long identifier. That is because a card is a property of a +cardholder. Do not read anything more into it: treat it as opaque.

+
+
+
+

Don’t delete cards: disable them

+
+

That wasn’t a great example because generally, when you have reason to stop a card from working, you +want:

+
+
+
    +
  • +

    a permanent reminder of why you did it,

    +
  • +
  • +

    to prevent another operator assigning the same card number to them later (so that if someone finds +a card on the ground and tries it, it won’t open the building), and

    +
  • +
  • +

    to know who a lost card was assigned to in case it turns up again.

    +
  • +
+
+
+

Command Centre achieves the first two of these goals if you delete an old card but it is easier if +you leave it card in the system, non-functional. You can set its end date into the past or set its +state to one of the disabled states.

+
+
+ + + + + +
+
Warning
+
+Deleting cards loses information about it, and can reduce the security of your building.
+Disable them instead. +
+
+
+
+
+
+

Group memberships

+
+
+

In this section you will add your cardholder to an access group and +modify the membership.

+
+
+

Add an access group membership

+
+

You will need the href of your cardholder that you used in the +cardholder detail GET or the +cardholder PATCH, or that came +back from your POST when you created a +cardholder.

+
+
+

You also need the href of your access group. You can see all your access +groups by querying the access groups controller. Hint: GET /api then look +in the block called accessGroups. Extra hint: GET /api/access_groups.

+
+
+

When you have those two hrefs, substitute them into:

+
+
+
+
// PATCH /api/cardholders/325 (1)
+{
+  "accessGroups": {
+    "add": [
+      {
+        "accessGroup": {"href": "https://localhost:8904/api/access_groups/5388"} // (2)
+        , "from": "2017-01-31T02:11:00Z"
+        , "until": "2037-01-31T02:11:00Z"
+      }
+    ]
+  }
+}
+
+
+
+
    +
  1. +

    is the href of your cardholder.

    +
  2. +
  3. +

    is the href of your access group.

    +
  4. +
+
+
+

(Remember that the first line does not go into the body of your HTTP query, and your actual URL will +start with https:// with a host and port. Also note the alternative comma style: putting them on +the start of the line makes commenting them out easier.)

+
+
+

If you use the wrong access group identifier, or your operator does not +have 'Modify Access Control' on the access group, you will be told:

+
+
+
+
{
+  "message": "Invalid access group href: https://localhost:8904/api/access_groups/53888"
+}
+
+
+
+

When you get it right, the server will return 204 and next time you GET +your cardholder the result will contain:

+
+
+
+
// GET /api/cardholders/325
+{
+  ...
+  "accessGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/access_groups/1069", // (1)
+      "accessGroup": {
+        "name": "Boney M",
+        "href": "https://localhost:8904/api/access_groups/5388"
+      },
+      "status": {
+        "value": "Active",
+        "type": "active"
+      },
+      "from": "2017-01-31T02:11:00Z",
+      "until": "2037-01-31T02:11:00Z"
+    }
+  ]
+}
+
+
+
+

The 'Cardholder access group' section of the cardholder API +documentation helps with interpreting that.

+
+
+

The marked URL is the href of the cardholder’s group memberrship, which is a link between the +cardholder (ID 325, in my case) and the access group (ID 5338). It starts with the href of the +cardholder, because it is a property of that cardholder and serviced by the cardholders controller, +but do not try to interpret it more. Certainly do not read anything into the number on the end +(1069), and do not be surprised if you have an item with the same ID.

+
+
+

The importance of /api and Identifiers in your app go into what you should not do with +hrefs.

+
+
+
+

Edit an existing group membership

+
+

Correct the URL of the cardholder and the access group membership in this PATCH:

+
+
+
+
//PATCH /api/cardholders/325
+{
+  "accessGroups": {
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/access_groups/1069",
+        "from": "2027-03-09"
+      }
+    ]
+  }
+}
+
+
+
+

If it returns a 204, GET your cardholder again and look at its access +group memberships. The from date should have changed from 2017 to 2027, +and the membership href will be different.

+
+
+

The server changes the href after an update to prevent race conditions +when there are two operators active. It means the two of you cannot +change the group membership at the same time - the second one in will +fail. The advice, therefore, is to update your cardholder as soon as +possible after retrieving its details (footnote: probably good advice +for a fetch and update on any API).

+
+
+ + + + + +
+
Caution
+
+Do not cache the hrefs of links between items. They change with operator actions. +
+
+
+
+
+
+

Create a cardholder, cont.

+
+
+

Now that you have access groups, cards, and PDFs, you can create a fully +configured cardholder in one request. Here is an example that creates a +cardholder, sets a PDF called 'email', puts it in an access group (which +is necessary for the PDF to work), and gives them a card.

+
+
+
+
// POST /api/cardholders
+{
+  "firstName": "New", "lastName": "Cardholder",
+  "description": "Test cardholder",
+  "division": {"href":"https://localhost:8904/api/divisions/2"}
+  "useExtendedAccessTime": true,
+  "usercode": "1234",
+  "@email": "a@b.com",
+  "accessGroups": [
+    {
+      "accessGroup": {"href": "https://localhost:8904/api/access_groups/334"}, // (1)
+      "from": "2019-01-01"
+    }
+  ],
+  "cards": [
+    {
+      "type": {"href": "https://localhost:8904/api/card_types/342"}, // (1)
+      "number":"3162"
+    }
+  ],
+  "zzzcompetencies": [ // (2)
+    {
+      "competency": {"href": "https://localhost:8904/api/competencies/5394"},
+      "enabled": true,
+    }
+  ]
+}
+
+
+
+
    +
  1. +

    These identify an access group and card type for your new cardholder.

    +
  2. +
  3. +

    Never mind the competency yet. The zzz makes the server ignore it.

    +
  4. +
+
+
+

You will need to change the marked numbers to the IDs of an access group and a card type on +your system. You can get those with:

+
+
+
+
GET /api/access_groups
+
+
+
+

and

+
+
+
+
GET /api/card_types
+
+
+
+

In Chrome, those calls will look like +https://yourhost:8904/api/access_groups and +https://yourhost:8904/api/card_types.

+
+
+

An actual application would also find the href of the correct division, +but for today is it safe to assume that the href of the root division is +…​/divisions/2.

+
+
+

After changing the 334 and the 342 put the JSON into Postman and POST it to +/api/cardholders. It should return you the href of a new cardholder, as it did in +Create a cardholder.

+
+
+

The zzz is in there to stop the REST API trying to add a competency to +your new cardholder, which would fail because you have not created a +competency yet. There is nothing special about three 'Z’s — the server +just ignores anything it does not recognise.

+
+
+ + + + + +
+
Important
+
+The server will ignore fields it does not recognise. Beware of this, as you may think +your calls are succeeding when in fact they ard doing less than you want them to. +
+
+
+

That is more of an advantage that a disadvantage. It means we can write +clients that degrade gracefully on Command Centre servers that are not +the most recent version or are missing licences. Also, introducing typos +to the names of your JSON objects is a convenient way of commenting them +out. You can also prepend lines with //. It is not valid JSON but you +can get away with it for now.

+
+
+

Back to our example. If you want to create a cardholder with a +competency:

+
+
+
    +
  1. +

    make a competency in the Configuration Client,

    +
  2. +
  3. +

    find its href from the competencies controller (GET /api/competencies),

    +
  4. +
  5. +

    change your JSON (remove the zzz and change the 5394), and

    +
  6. +
  7. +

    try the POST again.

    +
  8. +
+
+
+

It should fail, complaining that you cannot have two cards with the same card number. Change the +3162 and try again (or change number to znumber and let Command Centre pick a card number for +you — probably 3163).

+
+
+
+
+

Coding considerations

+
+
+

If you don’t mind a return to theory, here are several things you should keep in mind when building +an integration against this API.

+
+
+

Recap

+
+
    +
  • +

    /api returns links to summary pages. Why that is important is in +The importance of /api.

    +
  • +
  • +

    At time of writing, the URLs of most summary pages end with the name of the +controller, such as cardholders or access_groups. Others end with their +specific purpose, such as card_types/assign, which returns the card types your operator can assign +to people.

    +
  • +
  • +

    Summary pages show you many items without much detail of each. You can add sorting and pagination +parameters. Cardholders, alarms and +events, and other items. Tell the API to sort its results by ID +because it is quicker, and more reliable when operators are changing the database. Unless you’re +writing a user app and really must have your results sorted by name.

    +
  • +
  • +

    You can also add filters to summary pages, turning them into search pages. See +Search for a cardholder and Event filters.

    +
  • +
  • +

    In v8.00+ you can add fields from the details page to the summary page +of items, and in 8.40+, events. Or you can specify the exact fields you +need, if you want to save traffic.

    +
  • +
  • +

    You walk the result set using links named next and previous.

    +
  • +
  • +

    Detail pages give you more on an item, but only one item at a time. Their URLs end with short +alphanumeric identifiers. A cardholder, for example. There is not much +use for detail pages in the API after v8.00, since you can add all their fields to the summary pages.

    +
  • +
  • +

    To create a cardholder, POST the +cardholders controller. The body of the POST is pretty much the same as +you get from a GET of an existing cardholder, but with fewer fields.

    +
  • +
  • +

    To update a cardholder, PATCH its href. +That includes adding cards.

    +
  • +
  • +

    If you are changing PDFs or flat fields, the body of the PATCH looks a +lot like what you got from a GET to the same URL. Put @-symbols on the +front of your PDF names.

    +
  • +
  • +

    If you are updating cards, lockers, access groups, relationships, +competencies, or operator groups, you will be sending arrays called +add, remove, and update inside objects called cards, lockers, +etc.

    +
  • +
+
+
+
+

Only one cardholder at a time

+
+

In all these flat field, card, and group membership examples you were +working on one cardholder at a time. That is the only way you can +operate, because the cardholder you are changing is named by the URL. If +you want to change many cardholders, you must do it in a loop.

+
+
+

On the upside, you can change everything about the cardholder in one +PATCH. In fact, it is most efficient to do so. While you can use DELETE +to remove one card or group membership or relationship at a time, you +will see much better throughput if you combine it with the other changes +for that cardholder and send them as one PATCH. The same applies to +creating a cardholder: it is much quicker to do it as one POST than as a +POST followed by one or more PATCHes. The other advantage is that any +one is atomic: all the changes you put in the body happen, or none of +them do. So:

+
+
+ + + + + +
+
Tip
+
+When creating a new cardholder, do it all in one POST.
+When modifying an existing cardholder, do it all in one PATCH. +
+
+
+
+

The importance of /api

+
+

Forget all the URLs you have seen so far, except the first, and do not +write them into your applications. The only address that your +application should have coded into it is /api. You can learn every +other address you need with a GET of that. It will return a table of +contents like this:

+
+
+
+
// GET /api
+{
+  "version": "7.90.0.0",
+  "features": {
+    "accessGroups": {
+      "accessGroups": {
+        "href": "https://localhost:8904/api/access_groups"
+      }
+    },
+    "accessZones": {
+      "accessZones": {
+        "href": "https://localhost:8904/api/access_zones"
+      }
+    },
+    "alarms": {
+      "alarms": {
+        "href": "https://localhost:8904/api/alarms"
+      },
+      "updates": {
+        "href": "https://localhost:8904/api/alarms/updates"
+      },
+      "divisions": {
+        "href": "https://localhost:8904/api/divisions/view_alarms"
+      }
+    },
+    "alarmZones": {
+      "alarmZones": {
+        "href": "https://localhost:8904/api/alarm_zones"
+      }
+    },
+    "cardholders": {
+      "cardholders": {
+        "href": "https://localhost:8904/api/cardholders"
+      },
+      "updateLocationAccessZones": {
+        "href": "https://localhost:8904/api/access_zones/update_cardholder_location"
+      },
+      "changes": {
+        "href": "https://localhost:8904/api/cardholders/changes"
+      }
+    },
+    "cardTypes": {
+      "cardTypes": {
+        "href": "https://localhost:8904/api/card_types"
+      },
+      "assign": {
+        "href": "https://localhost:8904/api/card_types/assign"
+      }
+    },
+    "competencies": {
+      "competencies": {
+        "href": "https://localhost:8904/api/competencies"
+      }
+    },
+    "doors": {
+      "doors": {
+        "href": "https://localhost:8904/api/doors"
+      }
+    },
+    "events": {
+      "events": {
+        "href": "https://localhost:8904/api/events"
+      },
+      "updates": {
+        "href": "https://localhost:8904/api/events/updates"
+      },
+      "eventGroups": {
+        "href": "https://localhost:8904/api/events/groups"
+      },
+      "divisions": {
+        "href": "https://localhost:8904/api/divisions/view_events"
+      }
+    },
+    "fenceZones": {
+      "fenceZones": {
+        "href": "https://localhost:8904/api/fence_zones"
+      }
+    },
+    "inputs": {
+      "inputs": {
+        "href": "https://localhost:8904/api/inputs"
+      }
+    },
+    "items": {
+      "items": {
+        "href": "https://localhost:8904/api/items"
+      },
+      "itemTypes": {
+        "href": "https://localhost:8904/api/items/types"
+      },
+      "updates": {
+        "href": "https://localhost:8904/api/items/updates"
+      }
+    },
+    "lockerBanks": {
+      "lockerBanks": {
+        "href": "https://localhost:8904/api/locker_banks"
+      }
+    },
+    "macros": {
+      "macros": {
+        "href": "https://localhost:8904/api/macros"
+      }
+    },
+    "outputs": {
+      "outputs": {
+        "href": "https://localhost:8904/api/outputs"
+      }
+    },
+    "personalDataFields": {
+      "personalDataFields": {
+        "href": "https://localhost:8904/api/personal_data_fields"
+      }
+    },
+    "roles": {
+      "roles": {
+        "href": "https://localhost:8904/api/roles"
+      }
+    }
+  }
+}
+
+
+
+

You should parse the features block of that page for the URLs of the +calls you need. It contains one block for each controller in the API: +cardholders, events, alarms, etc. Some of those blocks contain one more +block, in turn containing an href for the base call for that controller. +Other controllers (card_types) contain more than one, each containing an +href for a different call. For example, the events and alarms +controllers also provide a divisions call which lists the divisions in +which your operator has the privilege to see events and alarms, +respectively.

+
+
+

Do not code URLs into applications

+
+

Because Gallagher reserves the right to change them. If you start at +/api, your application will stay compatible through Command Centre +upgrades.

+
+
+

While it is tempting to hard-code a string /api/cardholders into +your application, it is not that much more development effort to get +that URL from the contents page and make your code forward-compatible. +Help yourself to the sample C#, starting with ClientManagerAsync.cs.

+
+
+
+
+

Identifiers in your app

+
+

There are two API identifiers: IDs and hrefs.

+
+
+

The short alphanumeric string that comes in a field called id is there purely for use in query +parameters, such as filters. Because you add them to URLs they have to be short and free of +punctuation, so we do not use the longer identifier that comes next. Event filters shows how to +use them to find events.

+
+
+ + + + + +
+
Caution
+
+Do not treat IDs as integers. They are alphanumeric. A future version may add letters. +
+
+
+

The URL that comes in a field called href is how you reference +objects, both as addresses in your own GETs, DELETEs, and PATCHes, but +also in the bodies of those requests and POSTs when you need to connect +two objects. When adding a card to a cardholder, for example, you need +to send the href for the new card’s card type. You would find that href +using the card_types controller. As another example, when connecting two +cardholders with a relationship, you need to PATCH the href of one of +the cardholders with the href of the role (from the roles controller) +and of the other cardholder (from a search of cardholders).

+
+
+

Some hrefs are meant to 404

+
+

Many hrefs will respond to a GET, but some exist purely for +identification: cards, relationships, and group memberships, for +example. Those hrefs are for use inside the body of a PATCH to modify a +cardholder. You can DELETE some of them but GETting one of these will +always return 404.

+
+
+
+

Many hrefs are dynamic

+
+

Some hrefs change regularly: access group membership IDs, for example, +change every time you modify the underlying membership, even if you only +change its end-date. For that reason you must start all changes to a +cardholder with a GET of that cardholder. That will give you up-to-date +hrefs for linked group memberships, roles, cards, etc., which you can +then use in the body of a PATCH.

+
+
+
+

You can use them to cross-reference cardholders in an integration

+
+

External systems will have their own user identifiers: staff or student +ID numbers, usernames, or national IDs. You might like to store that ID +in a PDF and use the PDF search (/api/cardholders?pdf_yyy=zzz, +described in the developer documentation) to find the href of your +cardholder when it comes time to synchronise. Then you do not have to +store a copy of the href.

+
+
+

That could be slow for large numbers of cardholders. Instead, you could +retain the href returned to you when you created your cardholder and use +that forever after. There would be no need for a PDF holding the +external identifier inside Command Centre.

+
+
+

However that is no use for cardholders that your integration did not +create. Plus you risk losing your cardholder if its href ever changes +(which could occur if another operator or integration deletes and +recreates it, or Gallagher changes the layout of hrefs).

+
+
+

The recommended option is a blend of the two: give every cardholder a +PDF containing their external ID and cache their href externally. If +your cache does not have it, or if using it returns a 400-level error, +refresh your cache using a PDF search.

+
+
+
+

Subtract the host and port then replace them with values from your integration’s configuration

+
+

You will note that every cardholder href begins with the scheme, host, +and port: https://localhost:8904 in these examples. I am going to +contradict earlier advice ever so slightly and suggest that you drop the +https://host:port from the front of an href before storing it, then +add the host and port from your integration’s configuration before using +it again. By doing that you give yourself the flexibility to change the +hostname or port without invalidating your cache of hrefs.

+
+
+

For example, for a cardholder with href +https://localhost:8904/api/cardholders/123, store +api/cardholders/123 in your database. The application must have +locahost and 8904 in its configuration somewhere (how else could it +make HTTP requests?), so when it comes to find that cardholder again, +prepend https://localhost:8904/ to the stored value. When your IT +people change the server’s hostname or shift the service to another +port, all you have to do is change your configuration. Which you had to +anyway.

+
+
+
+
+

Do not build your own hrefs from IDs

+
+

Buoyed with confidence gained following links around our API, you will +be tempted to store just the parts of hrefs that seem to matter and +reconstruct them later. In the interests of forward compatibility:

+
+
+ + + + + +
+
Caution
+
+Do not interpret href paths, and do not build your own. We change them. +
+
+
+

As a reminder, the parts of a URL relevant to us are the protocol +('scheme'), host, port, path, and query:

+
+
+
+
scheme://host:port/path?query
+
+
+
+

The scheme will always be HTTPS: you can take that as read. By all means, +replace the hostname and port number and add your own search parameters +to the query on the end of URLs you take from GET /api, but please do +not tinker with the path. Treat that as opaque.

+
+
+

For example, in version 8.10 the path to a locker was

+
+
+
+
/api/locker_banks/locker_bank_id/lockers/locker_id
+
+
+
+

In 8.20 it changed to

+
+
+
+
/api/lockers/locker_id
+
+
+
+

Client code that inserts a locker bank ID and locker ID into the +hard-coded string /api/locker_banks/{1}/lockers/{2} will work against +an 8.10 server but fail when the server upgrades to 8.20. Client code +that takes the href from a locker bank page will work with both +versions.

+
+
+
+

Sort by ID, and get all summary pages without delay

+
+

By default, item summaries arrive sorted by name. That can cause a race +condition:

+
+
+
    +
  1. +

    You get the first 1000 cardholders, sorted by name.

    +
  2. +
  3. +

    Another operator (or your own update) changes the name of one of those +cardholders to part of the alphabet you have not collected yet, or vice +versa.

    +
  4. +
  5. +

    The next 1000 cardholders will either contain one you already +received, or will skip one you did not.

    +
  6. +
+
+
+

There are three things you can do to reduce this risk:

+
+
+
    +
  • +

    Sort by ID. Command Centre does not recycle IDs so no cardholder can +slip into part of the database you have already extracted.

    +
  • +
  • +

    Collect hrefs from the summary pages, following the next link until it +no longer arrives, before using any of them. This means you collect +everyone in the minimum possible time, and if you do update your +cardholders you will not mess in your own yard.

    +
  • +
  • +

    Get thousands at a time so that you make fewer calls.

    +
  • +
+
+
+
+

Monitoring for cardholder changes is much better in 8.30

+
+

Version 7.90 supports change tracking through the events API. If you +filter for operator events you will be informed of every change using +long polls (below): the href of the affected cardholder or access group +will be in the event. In 8.00+ the href of the operator who made the +change will also be in the event.

+
+
+

Version 8.30 adds a call to the cardholders controller that makes +synchronising them much simpler. It can tell you which fields changed on +a cardholder and what their values were before and after the change, and +what their current values are. All the details are in a section called +"Cardholder changes" in the developer documentation but here is a +quick run-down:

+
+
+
    +
  1. +

    Send a GET to request a bookmark to the current head of the list of +cardholder changes.

    +
  2. +
  3. +

    Synchronise your system with Command Centre using other cardholder +methods. It does not matter how long this takes.

    +
  4. +
  5. +

    GET the bookmark you received previously. That will send you all the +changes that happened since then, plus a new bookmark.

    +
  6. +
  7. +

    Process those changes, if there were any. Sleep if there were not.

    +
  8. +
  9. +

    Go back to step 3.

    +
  10. +
+
+
+

To reduce the work you have to do and chatter on the wire using filter +and fields query parameters. filter limits the changes you receive +to those that you’re particularly interested in (you might not care +about anything except changes to PDFs, for example), and fields lets +you request more or less data about each change and its cardholder.

+
+
+
+

Long polls

+
+

A long poll is a way for HTTP servers to send updates to interested +clients. The client registers its interest by sending an HTTP GET, and +the server pushes to the client by responding when something of interest +occurs.

+
+
+

Using telephone calls as an analogy, a traditional poll would have the +client calling the server, the server answering, then hanging up +immediately if there was nothing to report. The client would then need +to wait a time and try again.

+
+
+

If the telephone server supported long polls, however, it would leave +the incoming call ringing until it had something to say. The client +would carry on about its business until the server picked up. After +hearing the server’s response the client would call back when it wished. +Immediately if it was in a particular hurry.

+
+
+
+

Benchmarks

+
+

These are the results of informal performance tests of Command Centre +7.90 running on reasonably capable hardware.

+
+
+

You will not achieve these numbers without following the advice in the +'efficiency tips' sections of the developer documentation.

+
+
+

Extracting events

+
+

Sustained an average of two to four thousand per second from a database +of four million.

+
+
+
+

Extracting 12,000 cardholders

+
+

Extracting their cards, access groups, and PDFs took three to four +minutes on a v7.90 server. The process was to request a summary page of +10,000 cardholders, then the remaining 2,000, then iterate through all +their hrefs, getting their details pages one by one.

+
+
+

That is a poor approach these days. Extracting the same fields for the +same 12,000 cardholders took 12 seconds on the same server running +v8.00. This process used the fields parameter to add cards, access +groups, and PDFs to the summary page so that the test did not have to +get any detail pages.

+
+
+
+

Extracting 200,000 cardholders

+
+

Now on 8.30 and different hardware, extracting the names of 200,000 +cardholders took one minute with top=1000&sort=id, or 28 minutes +without. That is how important those query parameters are. Use them!

+
+
+

Part of the three-times speedup from the previous test will be due to the absence +of PDFs: they can be expensive to extract.

+
+
+
+

Creating cardholders

+
+

Ten thousand took an hour. The test added a cardholder with a card and a +handful of group memberships and PDFs.

+
+
+
+
+
+
+

Advanced events

+
+
+

Event filters

+
+

The developer documentation is authoritative on how to restrict your +event results, but here is an introduction.

+
+
+

You can filter by the occurrence date/time, the source item, the event’s division (which is almost +always the source’s division), the event’s type, the type’s group (all event types are grouped, and +picking a group is synonymous with picking a few types), or the event’s cardholder.

+
+
+

In 8.70 and later you can limit the events to those related to a particular item. For example, by +specifying a cardholder’s ID you will receive the same events that would appear in Command Centre’s +activity report for that cardholder.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To filter by …Add a query parameter called…

Event type

type

Event type group

group

Cardholder

cardholder

Source

source

Division

division

Date

after and / or before

Related item

relatedItem, and be running 8.70 or later.

+
+

For example, to find all card events ('access granted', 'access denied', +etc.):

+
+
+
+
GET https://localhost:8904/api/events?group=23
+
+
+
+

The API documentation shows you where the 23 comes from.

+
+
+

To watch two cardholders:

+
+
+
+
GET https://localhost:8904/api/events?id=325,8445
+
+
+
+
+

IDs to use in filters

+
+

To keep the query strings manageable, these filters take short, +alphanumeric strings as IDs rather than the URLs that the API generally +uses for identifying items. At the version of writing (8.70) these IDs are low numbers, +but we reserve the right to introduce letters in the future, so do not +interpret them as integers.

+
+
+

These are your options for finding the IDs you need to build a filter +string:

+
+
+
    +
  • +

    look at one of the events you want in the API. Everything you can filter by is there;

    +
  • +
  • +

    look at /api/events/groups for event types and their +groups;

    +
  • +
  • +

    look at /api/cardholders for cardholders;

    +
  • +
  • +

    look at /api/items for all other items, using a type filter of its own from looking at +/api/items/types;

    +
  • +
  • +

    if running v8.00 or later, look at the controllers for doors, outputs, alarm zones, access zones, +and fence zones, linked from /api;

    +
  • +
  • +

    If running v8.10 or later, look at the inputs controller.

    +
  • +
+
+
+

For example, to find all your doors you would

+
+
+
+
GET https://localhost:8904/api/doors
+
+
+
+

(after getting that URL from GET /api, of course)

+
+
+

The doors controller arrived in 8.00, so if you have 7.90 or older you would

+
+
+
+
GET https://localhost:8904/api/items?type=11
+
+
+
+

That 11 came from

+
+
+
+
GET https://localhost:8904/api/items/types
+
+
+
+
+

Filtering by date

+
+

Even though the before and after fields are only accurate to a second, +filtering by date is 'smart' for reports: the result set will not +include events that occurred during the before second. For example, +before=2019-01-01T00:00:00Z will not return you any events from 2019 or later years. +Pass the before parameter for one report as the after parameter of +the next. You never need to use 23:59:59, and there is no risk of +missing an event that happens in the last second, or in a leap second.

+
+
+

All date-times should be in ISO-8601. If you omit fields (such as +minutes or seconds) Command Centre will assume sensible defaults, but +the best advice is to be explicit (especially about the time zone).

+
+
+ + + + + +
+
Important
+
+Put a timezone specifier in all date-times! Otherwise the outcome will depend on settings +on the server. +
+
+
+
+

Adding PDF values to the cardholders in events

+
+

Card events such as 'access granted' use the door as the source but also +have a related cardholder. The event JSON includes the cardholder’s name +and href, but if you want to use your own identifiers for cardholders +you can also ask for a PDF to come out with the event. Do that by adding +fields=defaults,cardholder.pdf_XXXX where XXXX is the ID of the PDF. +Find that ID with a query to /api/personal_data_fields, adding +?name="your_pdf_name" if you want Command Centre to do the searching +for you.

+
+
+

In order to see that PDF, your REST operator will need the appropriate +privileges. Otherwise the event will come out without the PDF. 'View +Cardholder' on the cardholder might not be enough: while PDFs are +visible by default, an operator can hide them, in which case your REST +client’s operator group will need to override that to readable or +read/write.

+
+
+
+

Writing an interactive event viewer

+
+

If I was writing an interactive application to monitor events as they +occurred, while also allowing browsing the event history, I would get +the most recent—​enough to fill a screen—​with:

+
+
+
+
GET /api/events?previous=true&top=20
+
+
+
+

Then I would set an asynchronous task waiting on the updates link, +which would return with new events as they happened.

+
+
+

At the same time I would follow the next and previous links to +collect more events as my user scrolled back and forth.

+
+
+
+

Worked example: reading alarms

+
+

What follows is a series of calls that collect alarms from the 7.80 +version of the alarms API. There may be extra fields in later versions +of Command Centre.

+
+
+

Collecting all unprocessed alarms with one active forced door

+
+

The initial HTTP GET of http://localhost/api/alarms returns all +unprocessed alarms: a bad login, a network problem, and two forced +doors, in this example. The second forced door is still open, so the +alarm is active and instead of links for processing it we have links for +force processing it, because you are not really meant to process active +alarms.

+
+
+

Note they are in the order that they arrived at the server, not the +order they happened. Interesting pieces are in bold face.

+
+
+
+
{
+  "alarms": [
+    {
+      "href": "http://localhost:8904/api/alarms/289",
+      "id": "289",
+      "time": "2016-11-10T14:17:00",
+      "message": "Operator logon failed for FT Workstation on GNZ-PC1302",
+      "source": { "name": "FT Workstation on GNZ-PC1302" },
+      "type": "Operator Logon Failed",
+      "priority": 3,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/289/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/289/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/289/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/289/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/289/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/289/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/296",
+      "id": "296",
+      "time": "2016-11-10T13:58:16",
+      "message": "Fat controller - Command Centre comms interrupted",
+      "source": { "name": "Fat controller" },
+      "type": "Comms failed to Command Centre",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/296/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/296/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/296/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/296/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/296/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/296/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/301",
+      "id": "301",
+      "time": "2016-11-10T14:18:27",
+      "message": "Warehouse door has been forced.",
+      "source": { "name": "Warehouse door" },
+      "type": "Forced Door",
+      "priority": 8,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/301/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/301/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/301/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/301/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/301/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/301/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/306",
+      "id": "306",
+      "time": "2016-11-10T14:21:41",
+      "message": "Front door has been forced.",
+      "source": { "name": "Front door" },
+      "type": "Forced Door",
+      "priority": 8,
+      "state": "unacknowledged",
+      "active": true,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/306/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/306/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+      "forceProcess": { "href": "http://localhost:8904/api/alarms/306/process" }
+    }
+  ],
+  "updates": { "href": "http://localhost:8904/api/alarms/updates?id=306" }
+}
+
+
+
+
+

Collecting updated alarms after closing the door

+
+

Next we close the front door, the kicking in of which caused alarm 306, +and GET the updates URL at the end of the previous result, +http://localhost/api/alarms/updates?id=306. Because the alarm is no +longer active we do not have a link for force-processing it; instead we +have links for processing it normally with or without comments.

+
+
+
+
"updates": [
+  {
+    "href": "http://localhost:8904/api/alarms/306",
+    "id": "306",
+    "time": "2016-11-10T14:21:41",
+    "message": "Front door has been forced.",
+    "source": { "name": "Front door" },
+    "type": "Forced Door",
+    "priority": 8,
+    "state": "unacknowledged",
+    "active": false,
+    "division": { "href": "http://localhost:8904/api/divisions/2" },
+    "view": { "href": "http://localhost:8904/api/alarms/306/view" },
+    "comment": { "href": "http://localhost:8904/api/alarms/306/comment" },
+    "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+    "acknowledge": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+    "processWithComment": { "href": "http://localhost:8904/api/alarms/306/process" },
+    "process": { "href": "http://localhost:8904/api/alarms/306/process" }
+  }
+],
+"next": { "href": "http://localhost:8904/api/alarms/updates?id=306.1" }
+
+
+
+
+

Updating after cutting power

+
+

This is the result of http://localhost/api/alarms/updates?id=306.1 (the next link from the +previous results) after cutting power to the controller and waiting a minute for Command Centre to +raise an alarm about it. Nothing has changed on the previous alarms so they do not come out.

+
+
+
+
"updates": [
+  {
+    "href": "http://localhost:8904/api/alarms/308",
+    "id": "308",
+    "time": "2016-11-10T14:35:21",
+    "message": "Controller \"Fat controller\" Offline.",
+    "source": { "name": "Fat controller" },
+    "type": "Controller Offline",
+    "priority": 6,
+    "state": "unacknowledged",
+    "active": true,
+    "division": { "href": "http://localhost:8904/api/divisions/2" },
+    "view": { "href": "http://localhost:8904/api/alarms/308/view" },
+    "comment": { "href": "http://localhost:8904/api/alarms/308/comment" },
+    "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+    "acknowledge": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+    "forceProcess": { "href": "http://localhost:8904/api/alarms/308/process" }
+  }
+],
+"next": { "href": "http://localhost:8904/api/alarms/updates?id=308" }
+
+
+
+
+

Updating after restoring power

+
+

Next we GET http://localhost/api/alarms/updates?id=308 (the next +link from the previous results, again) after restoring power to the +controller and waiting for it to come online.

+
+
+

The 'controller offline' alarm (ID 308) has changed to inactive since +the controller has reappeared on the network.

+
+
+

The 'low power' alarm has arrived from the controller carrying a timestamp from when it lost mains +power, while it was running on internal reserve power, which was one minute earlier than the +'controller offline' alarm.

+
+
+

Bringing up the rear is another alarm that the controller generated when +it restarted.

+
+
+
+
{
+  "updates": [
+    {
+      "href": "http://localhost:8904/api/alarms/308",
+      "id": "308",
+      "time": "2016-11-10T14:35:21",
+      "message": "Controller \"Fat controller\" Offline.",
+      "source": { "name": "Fat controller" },
+      "type": "Controller Offline",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/308/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/308/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/308/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/308/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/310",
+      "id": "310",
+      "time": "2016-11-10T14:34:01",
+      "message": "Fat controller - power low.",
+      "source": { "name": "Fat controller" },
+      "type": "Controller power low",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/310/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/310/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/310/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/310/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/310/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/310/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/313",
+      "id": "313",
+      "time": "2016-11-10T14:35:49",
+      "message": "Controller \"Fat controller\" restarted after power failed.",
+      "source": { "name": "Fat controller" },
+      "type": "Power failed",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/313/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/313/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/313/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/313/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/313/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/313/process" }
+    }
+  ],
+  "next": { "href": "http://localhost:8904/api/alarms/updates?id=313" }
+}
+
+
+
+

That illustrates how events' occurrence times can be out of order when the devices sending them to +the database are out of sync.

+
+
+
+
+
+
+

Client-side certificates

+
+
+

First see Authentication and encryption certificates for what certificates are and the +difference between server certificates and client certificates.

+
+
+

This section deals with the client certificate check that happens if you +left 'Do not require pinned client certificates' off in the 'Web +Services' property tab of an 8.40 server, or if you entered a thumbprint +into a REST Client item in 8.50 or later.

+
+
+

The process in The request process shows that +if you have not disabled client certificate checking, the server does it +after extracting the API key. If the server does not have the client’s +certificate pinned to the REST Client item with that API key, it will +reject the request and raise an alarm:

+
+
+
+
A REST connection was attempted with an invalid client certificate
+
+
+
+

That alarm will be at the same priority as a controller disappearing off +the network, which should cause a stir, so try not to do it in +production.

+
+
+

The next two sections should help you decide whether to use the feature. +The sections following those contain sample command lines that you can +paste into a shell on your clients to create client certificates on disk +or in the Windows certificate store. If you receive syntax errors, you +may have an old version of the software (I have had problems with +New-SelfSignedCertificate on Windows) or the hyphens may not be +hyphens: they may come through as dashes, which look very similar to us +but not to shells. You may have to re-type them.

+
+
+

The bold in the sample command lines reduce the protection around your +private key. That may be acceptable in a development environment but for +proper security in a production environment you should omit the bold +parts.

+
+
+

What client certificate checking does

+
+

When not disabled by the checkbox in the server +properties, Command Centre requests proof from the client that it has the private key that matches a +public key that the server has configured into it (pinned). A public key is hundreds of bytes so you +don’t want to paste the whole thing into Command Centre’s configuration, and we do not want to +compare all those bytes for each request, so you enter a shorter form instead. It is known as the +certificate’s thumbprint or fingerprint and is a cryptographic hash of the whole +certificate. It is impossible for a client to send a fake certificate with a thumbprint that looks +real.

+
+
+

Certificates can also contain a chain of trust linking the certificate +back to a trusted authority. A client uses a server certificate’s chain +of trust to check the identity of the server that responded to its +request. It does not work in reverse: servers do not check that part of +a client’s certificate. You would not have pasted the certificate’s +thumbprint into Command Centre if you did not trust it.

+
+
+

When you enter a thumbprint into a CC REST Client’s property page in the +Configuration Client or leave the checkbox we are covering next +unchecked in the server properties, you are saying that only the caller +who has the matching private key is allowed to use that REST Client. In +other words, the client software must possess two secrets that the +server can verify: the API key and the private key.

+
+
+

Before getting into why you want all this checking happening, we should +cover how to turn it on. Or, since the product ships with it turned on, +why you should not turn it off.

+
+
+

The mysterious client certificate checkbox

+
+

In 'Server Properties', which you get to in the Configuration Client +starting with the 'File' menu or the top item in your hardware tree, +there is a tab called 'Web Services'. In there, in the box headed by +'Enable REST API', is a checkbox. In 8.40 it was called 'Require pinned +client certificates'. In 8.50 it is called 'Enable REST Clients with no +client certificate'.

+
+
+

It behaves like this:

+
+ +++++ + + + + + + + + + + + + + + + + + + + +
8.40'Do not require pinned client certificates' off'Do not +require pinned client certificates' on

Connection attempt to a REST Client item with a certificate +thumbprint configured

Client certificate checked

Client certificate +ignored, connection accepted

Connection attempt to a REST Client item without a certificate +thumbprint configured

Connection rejected

Connection accepted

+
+

In 8.50, the top-right quadrant changed.

+
+ +++++ + + + + + + + + + + + + + + + + + + + +
8.50'Enable REST Clients with no client certificate' off'Enable +REST Clients with no client certificate' on

Connection attempt to a REST Client item with a certificate +thumbprint configured

Client certificate checked

New in 8.50: client +certificate checked

Connection attempt to a REST Client item without a certificate +thumbprint configured

Connection rejected

Connection accepted

+
+

That change will have negatively affected sites that had 'Do not +require…' turned on, but also had thumbprints (uselessly) configured +into their client items. However there is a huge upside to the change: +in 8.50 or later a site can have some clients using client certificates +and some not.

+
+
+
+
+

Why use client certificates?

+
+

To make it harder for an attacker to masquerade as a legitimate REST +client.

+
+
+

To do that, they must obtain your API key at the very least. There are +more barriers that you can put up:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + +
If you:…the black-hat will then have to:

use a firewall (Windows or hardware)

be on the server network.

use an IP filter

spoof the source IP.

pin your client’s certificate

have a copy of the client’s private key.

limit your application privileges

settle for less access.

+
+

Pinning a client certificate is one more hoop an attacker has to jump +through.

+
+
+

You should make viewing your private key very difficult for anything +that does not need it. Do not leave it in the filesystem for anyone to +read! If you are running on Windows, you should use the certificate +store. If you are on another O/S, protect the key while it is on disk +with filesystem permissions and by encrypting it with a password hidden +in your application.

+
+
+
+

Create a certificate and record the thumbprint in CC

+
+

The following commands create a client certificate. You need to run them on the +system that will be running your REST client. That may not be your Command +Centre server.

+
+
+

Using OpenSSL tools

+
+

This method works equally well on Unix-like or Windows systems with OpenSSL +installed, but the later sections might serve Windows people better because +they show how to put the certificate directly into the Windows certificate +store.

+
+
+ + + + + +
+
Warning
+
+The OpenSSL commands in this section put the private key on disk, +which should make you a bit nervous if you are doing it in production. On +a Unix-like system you could do it in a mode-0700 folder on a filesystem +that is not backed up and is cleared during a reboot, such as /tmp. +
+
+
+
+
openssl req -x509      \
+  -newkey rsa:4096     \
+  -sha256              \
+  -nodes               \
+  -keyout rest.pem     \
+  -out rest.pem        \
+  -subj "/CN=RESTtest" \
+  -days 3650
+
+
+
+

Notice the rsa:4096: it produces a four-kilobit key, which is huge. It might be +overkill for development, but it’s nice to have the option.

+
+
+

Its name will be 'RESTtest'. You should come up with a name related to your application’s purpose +because diagnising certificate problems is a tough enough job without ambiguous certificate names +muddying the waters even more.

+
+
+

Again, the bold part is reducing your security. In this case, +the -nodes option (footnote: it means 'no DES'. It is not the plural +of 'node') means there is no password on the private key. Anyone could +read it from the rest.pem file, so in a production environment you should omit +the -nodes option and type in a password (a really good one) when +openssl req prompts you.

+
+
+

To get the thumbprint for Command Centre:

+
+
+
+
$ openssl x509 -fingerprint -in rest.pem -noout
+
+
+
+

If you protected the PEM with a password, openssl x509 will ask you +for it.

+
+
+ + + + + +
+
Note
+
+The 20 bytes that come out are what you paste into the REST Client +item in Command Centre. +
+
+
+

Now you need to add the certificate to the clients that need it. If you use Postman, +see Use the certificate in your client. If you use +Chrome on Windows, you need to add it to the certificate store with +these two commands:

+
+
+
+
openssl pkcs12 -export -in rest.pem -out rest.pfx -passout pass:
+explorer rest.pfx
+
+
+
+

The first command converts the PEM file into a file format that Windows +prefers. The -passout pass: option means it will not put a password on +it, so it is just as dangerous as the PEM file.

+
+
+

The second line will open rest.pfx in Explorer (the same as +double-clicking it) to import it into the certificate store. The default +options are good so you can click Next to let it use the current user, +determine the certificate store automatically, and mark the private key as +not exportable).

+
+
+

Finally, for goodness’ sake, protect the rest.pem and rest.pfx +files. Preferably delete them. Even better, use an eraser utility.

+
+
+
+

Using Windows tools

+
+

If your client is on a Windows host there are two more ways to create a +certificate and place it into Windows’s certificate store. Obtaining the +private key from there is easy for your client program but difficult for +anyone else, Microsoft assures us.

+
+
+
Powershell
+
+

The topic 'Creating the Client Certificate' in the Configuration +Client’s online help contains instructions for doing it in a PowerShell +with New-SelfSignedCertificate. Handily, it prints the thumbprint to +the console so you can copy it into Command Centre. You should try that +first, since it is simplest. But New-SelfSignedCertificate is not +present on all versions of Windows, so here is an alternative using…

+
+
+
+
makecert
+
+

…which has been around for longer.

+
+
+
    +
  1. +

    Run a developer command prompt as administrator. If you do not have a developer command prompt, +try a regular command prompt (as administrator).

    +
  2. +
  3. +

    In it:

    +
    +
    +
    makecert ignoreme.der
    +         -a sha1
    +         -ss My
    +         -sky signature
    +         -pe
    +         -len 2048
    +         -n "CN=RESTClientCert"
    +         -sr CurrentUser
    +
    +
    +
    +

    That will create a certificate and place it in your certificate store +with a copy on disk.

    +
    +
    +

    Its name will be 'RESTClientCert'. As previously indicated, you should come up with an informative +name for your certificates so that the brave souls diagnosing your TLS issues have something to +go on.

    +
    +
    +

    The -pe marked the key as exportable. More on that later.

    +
    +
  4. +
+
+
+

You do not need to keep the file ignoreme.der, but the easiest way to +get the thumbprint of your new certificate is to open ignoreme.der by +double-clicking on it in Explorer, go to the Details tab, scroll to the +bottom, and click the thumbprint. You could then skip the next three +steps, but when starting out it is a good idea to perform these steps to assure yourself +that makecert put your new certificate where it should have.

+
+
+
    +
  1. +

    Run mmc, add the Certificates snap-in to manage "My user account," +open it and then expand your "Personal" certificates.

    +
  2. +
  3. +

    Ensure you can see a certificate called (issued to) 'RESTClientCert' in there. +This is the cert you will pick for your browser later.

    +
  4. +
  5. +

    Double-click it, go to the Details tab, scroll to the bottom, and +click the thumbprint.

    +
  6. +
+
+
+ + + + + +
+
Note
+
+Those are the 20 bytes / 40 characters that you paste into the REST Client item in Command Centre. +
+
+
+
+
Aside: other ways of calculating the thumbprint
+
+

For your information, here are three more command-line options for +extracting the thumbprint from the DER file if you didn’t get it above. +They all do the same thing. Use whichever works for you:

+
+
+
+
openssl x509 -in ignoreme.der -inform der -noout -fingerprint
+openssl sha1 ignoreme.der
+sha1sum ignoreme.der
+
+
+
+

As you can see from the last two, a certificate thumbprint is really +just the SHA1 hash of the certificate when it is stored in a DER file.

+
+
+ + + + + +
+
Caution
+
+Some utilities, inluding openssl, print a thumbprint with colons separating each octet. +Take the colons out before pasting that into the REST Client item, otherwise your client will not be +able to make calls through the cloud API gateway. +
+
+
+
+
Marking keys as exportable
+
+

The -pe option to your makecert command above marked your private +key as exportable.

+
+
+

You can mark it as not exportable so that the standard utilities will not +be able to get it out of the certificate store. That sounds like a good +idea, because (while there are programs out there that export +non-exportable certificates) anything you can do to make the black-hat’s +job harder is a win.

+
+
+

If you used the command New-SelfSignedCertificate in Windows +Powershell (using the instructions in the Configuration Client’s user +guide), you can mark the certificate not exportable by adding +-KeyExportPolicy NonExportable to the command line.

+
+
+

The trouble is that in order to use your client certificate in Postman, +you have to export the key.

+
+
+

If you used makecert, remove the -pe and your new key will not be +exportable.

+
+
+
+
+

Extract the certificate and private key from the Windows store to disk

+
+

If you are going to use Postman you need to give it files containing +your private key and certificate, but if you are on Windows and used one of the Windows +utilities to create a new certificate in the certificate store, +you will not have the private key on disk. You will need to extract it.

+
+
+
    +
  1. +

    Run mmc. Certificates → Current User → Personal → Certificates.

    +
  2. +
  3. +

    Right-click your certificate → All tasks → Export…
    +Select the option to export the private key. Give it a password, +otherwise openssl cannot decrypt it.
    +It does not matter what you do with the other certificates, so leave the +defaults set.
    +Export it to a .pfx file on disk.

    +
    +

    That PFX file is partly secure because you put a password on it, but I expect +that password was very short so, again, be careful what you do with that +file.

    +
    +
  4. +
  5. +

    For old versions of Postman you may have to convert that PFX into a +file it understands. The current version of Postman does not need this:

    +
    +
    +
    $ openssl pkcs12 -in restexported.pfx –out rest.pem –nodes
    +
    +
    +
  6. +
+
+
+

It will ask you for the password you picked for the export. It will put +the certificate and the private key in the PEM file, unencrypted +(because of -nodes). It is plain text: you can look at it in Notepad.

+
+
+
+

An easy (but not so secure) way to discover your client certificate’s thumbprint

+
+

Create a client certificate using one of the methods above and use it in +an API call. The server should raise an alarm, complaining that 'a REST +connection was attempted with an invalid client certificate'. The rest +of that message will tell you which REST Client Item you need to put the +thumbprint on, and the alarm’s details string will contain the thumbprint +itself.

+
+
+

If the thumbprint is (null), your client did not send a certificate +at all.

+
+
+

Otherwise, copy the thumbprint straight out of there and paste it into +the item. The next time you try your call the server should not complain +about the certificate.

+
+
+

The reason this method is not so secure is that you might not be sure that +the alarm was yours. Someone else may have hit the API before you did.

+
+
+
+
+

Use the certificate in your client

+
+

Postman

+
+

The standalone version of Postman cannot read certificates out of the +certificate store (footnote: the Chrome extension can, but that version of +Postman is no longer under development).

+
+
+

Go to the cog → Settings → Certificates. Add the certificate that +Postman should use when talking to your server and port. Where Postman +asks for the CRT file, give it the file containing the certificate. Where +Postman asks for the key file, give it the file containing the private key. +They will both be the same file if you followed the example above.

+
+
+

If you protected your private key with a password (a good idea, but turned +off by -nodes), give it to Postman.

+
+
+
+Tell Postman which client cert to use +
+
Figure 18. Tell Postman which client cert to use
+
+
+

Now Postman will use that certificate when it talks to Command Centre. +If you put the certificate’s thumbprint on the REST Client item with the +API key Postman is using, you can turn on pinned certificates in the +server properties and Postman will still be able to connect.

+
+
+

You can leave Postman using this certificate no matter whether CC has +pinned certificates turned off or on: it does no harm.

+
+
+
+

Chrome

+
+

When you first try to connect to Command Centre using Chrome it will +give you a list of certificates in the store and ask you which to use. +Select the one you just put there.

+
+
+
+

wget

+
+
+
$ wget                                                                        \
+    --no-check-certificate                                                    \
+    --certificate=your_pem_file                                               \
+    --header="Authorization: GGL-API-KEY your-API-key"                        \
+    https://localhost:8904/api
+
+
+
+

The --no-check-certificate turns off client-side checking of the +server certificate.

+
+
+

Careful: my version of wget does not complain if it cannot read the +certificate file.

+
+
+
+

curl

+
+
+
$ curl                                                                        \
+    --verbose                                                                 \
+    --insecure                                                                \
+    --cert your_pem_file                                                      \
+    --header "Authorization: GGL-API-KEY your-API-key"                        \
+    https://localhost:8904/api
+
+
+
+

The --insecure turns off client-side checking of the server +certificate.

+
+
+

I found --verbose necessary to see error codes.

+
+
+
+
+
+
+

Server-side certificates

+
+
+

A client in a production environment should refuse to talk to a server that it does not recognise. +"Recognising" a server means either

+
+
+
    +
  • +

    having a copy of its certificate on the client already (which is the same as the client +certificate pinning covered in the previous section, only it is the client doing it to the server +instead of the server doing it to the client), or

    +
  • +
  • +

    having a trusted Internet authority’s signature on the certificate.

    +
  • +
+
+
+ + + + + +
+
Warning
+
+Production clients should check server certificates. If not, an attacker could +masquerade as the server. The client would send it its API key to the fake server, and the attacker +could—​if the real server was not checking client certificates—​use it for calls of its own. +
+
+
+

But if you are working in a development rig and your server does not have a signed certificate, you +might like to tell your client to skip the server certificate check, as you told Chrome and Postman +to do earlier.

+
+
+

If you will pardon a very brief dive into source code, here is one way to do it in a C# client:

+
+
+
+
ServicePointManager.ServerCertificateValidationCallback = delegate (
+    object s,
+    X509Certificate certificate,
+    X509Chain chain,
+    SslPolicyErrors sslPolicyErrors)
+  { return true; };
+
+
+
+

If you do not have control of the client application, or you wish to check the server certificate +(which is advisable), here are a couple of approaches to making the check succeed.

+
+
+

You only need one of these.

+
+
+

Pin the server certificate on the client

+
+

Go to Server Properties in Command Centre’s configuration client, then Web Services → Manage +Certificates, and click View on whichever type of certificate you are using. Go to the Details tab +in the window that appears, then choose Copy to File. You have a few choices for the format to +export it to. DER is good for Windows machines, but a PFX—​if that option is enabled—​might be +more widely accepted on non-Windows systems. If you are ever asked if you want to export the private +key, just say "no".

+
+
+

Once you have your certificate file on disk, copy it to the client +machine. Provided you didn’t let a private key get in there, it is not a +secret.

+
+
+

How you install it on the client depends on the client. On a Windows +box, it may be as simple as double-clicking it. The Certificate Import +Wizard will ask you where to install it: you probably want Trusted Root +Certificate Authorities. That is a bit of a sledgehammer, because it +makes not only your REST client but every client on that host trust that +certificate. Plus, they will trust any other certificate signed by it. +But it will get you going.

+
+
+

You have more options if you are writing your own client. You might like to have a copy of the +server’s certificate on disk to compare against. A certificate is large, so the usual approach is +to record a hash of it instead. It is not a secret but you must prevent anything from changing it, +because when your client connects to the server you want to hash the server’s certificate again and +compare it to the value you have on record. If they are not the same, don’t trust the server.

+
+
+

Windows calls the SHA1 hash of certificate its thumbprint, and presents it in the Details tab of +the window the first paragraph took you to: Server Properties → Web Services → Manage +Certificates → View → Details. You’ll find the thumbprint at the bottom of the field list (you +will have to scroll).

+
+
+
+

Buy a signed certificate for the server

+
+

If that method does not +suit, perhaps because you do not have control of the clients, you could +buy a "real" certificate for your CC server. One drawback is that +signatures eventually expire, requiring you to do this every year or +three. Another is that you must buy a new certificate if you change the +name of your server. So, use a DNS alias.

+
+
+

This is nothing new for REST APIs: it is the process that every web +site owner goes through. If you want to sell scones and Toby mugs from +www.itsabritishthing.com, for example, you:

+
+
+
    +
  1. +

    generate a certificate for www.itsabritishthing.com,

    +
  2. +
  3. +

    generate a certificate signing request (a CSR) from that certificate,

    +
  4. +
  5. +

    decide which signing authority to use,

    +
  6. +
  7. +

    send the CSR to that signing authority, with your credit card details and proof that you own +itsabritishthing.com,

    +
  8. +
  9. +

    wait for them to send you your new certificate, then

    +
  10. +
  11. +

    install it on your web server.

    +
  12. +
+
+
+

That is exactly what you will need to do for Command Centre. Start with +the DNS alias for your server instead of www.itsabritishthing.com. The +next few steps are a Googling exercise for the reader because they +depend on which authority you choose. Finish with a simple process +covered in the section called "Replacing the web service certificate" +of the Configuration Client’s user guide. Briefly, it is:

+
+
+
    +
  1. +

    Go to Server properties → Web Services → Manage Certificates in the REST API section,

    +
  2. +
  3. +

    change the radio box to Custom Certificate,

    +
  4. +
  5. +

    click Import, and

    +
  6. +
  7. +

    browse to your certificate file.

    +
  8. +
+
+
+

At time of writing, Gallagher Group has no association with +www.itsabritishthing.com.

+
+
+
+
+
+

Other Command Centre items

+
+
+

Version 8.00 added fence zones, access zones, alarm zones, doors, +macros, and outputs (relays and LEDs) under two licences. The RESTStatus +licence lets you see their status and some basic configuration, and the +RESTOverrides licence lets you send overrides to them. So you can open +doors, run macros, disarm fence and alarm zones, toggle outputs, and so +on. Version 8.10 added inputs, which are often connected to physical +devices such as reed switches and infrared sensors. Version 8.30 added a +method /api/items/updates which lets you monitor several items with +one connection. Version 8.50 added operator groups, schedules, and day +categories.

+
+
+

Note that the REST API does not let you create, edit, or delete any of +these items except schedules. The configuration client is still the +place for that.

+
+
+

Each of those item types has its own controller, its own block of links +in /api, and its own section in the developer documentation. +Schedules, day categories, and the items with status like zones and +hardware have their documentation under "Status and Overrides". +Because the items/updates method is on the items controller it in with +Alarms and Events. Operator groups are in with cardholders.

+
+
+

As an example of how to use these APIs, here is how you would list all +your doors and get the link to open one of them:

+
+
+
+
GET /api
+// You would find and use the URL at features.doors.doors.href, which in 8.30 is:
+GET /api/doors
+// To search for one door use the 'name' parameter:
+GET /api/doors?name="Greendoor"
+// But if all you intend to do is open it, you only need the override links:
+GET /api/doors?name="Greendoor"&fields=commands
+
+
+
+

Overriding items

+
+

To send an override to an item you make an HTTP POST to a URL that you +get from the item itself.

+
+
+

The output from the last example above, that requested the commands +block of the door called 'Greendoor', is:

+
+
+
+
"results": [
+  {
+    "commands": {
+      "open": {
+        "href": "https://localhost:8904/api/doors/507/open"
+      },
+      "free": {
+        "href": "https://localhost:8904/api/access_zones/533/free"
+      },
+      "freeUntil": {
+        "href": "https://localhost:8904/api/access_zones/533/free"
+      },
+      "freePin": {
+        "href": "https://localhost:8904/api/access_zones/533/free_pin"
+      },
+      "freePinUntil": {
+        "href": "https://localhost:8904/api/access_zones/533/free_pin"
+      },
+      "secure": {
+        "href": "https://localhost:8904/api/access_zones/533/secure"
+      },
+      "secureUntil": {
+        "href": "https://localhost:8904/api/access_zones/533/secure"
+      },
+      // ... ten more commands omitted for brevity ...
+      "cancel": {
+        "href": "https://localhost:8904/api/access_zones/533/cancel"
+      }
+    }
+  }
+]
+
+
+
+

Normally a search would return an ID, href, and name, and it would not +return that block of commands, but we turned that on its head by using +the fields query parameter to request the commands block and nothing +else. Each of the objects inside it is a named command containing an +href which, when you POST to it, sends an override to the item. For +example, if that was a door on your system and you pasted the URL from +commands.open into Postman, and POSTed it, the unlock relay on that +door would fire.

+
+
+

Each item type has a different set of commands you can send it. They +vary in type and number: outputs have four and access zones have 21. +Most of those access zone overrides are also available on the zone’s +doors, for convenience.

+
+
+

Overrides don’t need anything in the body of the POST, but those with +'Until' in the name of the command will use a timestamp if you send it:

+
+
+
+
// POST /api/access_zones/533/free
+{
+  "endTime": "2020-03-06T00:00:00Z"
+}
+
+
+
+

That example would put the door’s entry access zone in free mode until +midnight March 6.

+
+
+
+

Status flags

+
+

Just as each item type has its own commands, each also has its own set +of status flags. A door can be open or closed, for example, while an +access zone can be secure or free. Each also has its own set of flag +rules that they will always follow. Doors, inputs, and outputs are quite +simple but fence zones have half a dozen rules thanks to the voltages +they deal with.

+
+
+

The developer documentation clearly lays out all the status flags items +can return, and their rules. For example, here is part of the section on +outputs:

+
+
+
+
+

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 means the output is under the effect of an override.

    +
  • +
+
+
+

If and only if the output is online, one of relayStateUnknown, +closed, or open will appear. Of the above, only overridden can +appear when the output is offline.

+
+
+
+
+

The above tells you that the first flags you should look for are +relayStateUnknown, closed, and open. If none of those is in the +flag set then your output is offline. Other flags will tell you what the +problem is, if you want to go deeper, but it is probably enough for your +integration to know that the output’s state is uncertain and it should +subscribe to updates in case that changes.

+
+
+

At last count there were eleven status flags common to all items. Some +are not so serious, like the flags that indicate the item is shunted +(muted) or is not fully configured yet. Others indicate an actual +problem like a network outage, a cable fault, or a service not running. +The developer documentation covers them all (search for 'abnormal +status').

+
+
+

There is a lot more on the topic in the reference documentation.

+
+
+
+

Subscribing to updates (one item at a time, pre-8.30) TODO

+
+

This section TODO. What follows is a broad outline of what the section +should contain.

+
+
+

GET the updates link on an item’s details page. It is a long poll, so +the server won’t respond until it has something for you (or it times out +after about 50s). Then stay up to date by entering a loop GETting the +next link.

+
+
+

You really should be using bulk item updates, in the section below, because this method requires one +TCP connection per monitored item. Why use this?

+
+
+
    +
  • +

    You’re not running 8.30 yet, or

    +
  • +
  • +

    you’re monitoring only one item and want a slightly simpler call, or

    +
  • +
  • +

    your client wants to wait longer than 30s between GETs (but it is hard +to imagine a client needing that).

    +
  • +
+
+
+
+

Subscribing to updates (many items, 8.30+) TODO

+
+

A rough outline: GET /api then POST a document to the link at +features.items.updates (which is /api/items/updates in 8.30, but may +change, which is why you should use the page at /api).

+
+
+

The body of your POST should look like this:

+
+
+
+
{  "itemIds": ["508", "526"]  }
+
+
+
+

Those numbers are item IDs. Even though they look like integers they +must be in quotes because in the future they could contain alphas. Place +as many in the array as you like. We tested 1,000 without it affecting +performance.

+
+
+

The POST will return with the status of all your items and a next link. +GET that link, and keep GETting it in a loop to stay up to date. The +calls will block if there are no changes to report. Sleep between calls +to avoid tight loops. But not longer than 30s, otherwise the server will +drop your session, thinking you have walked away.

+
+
+

The first GET will return the same states that the POST did, which seems +redundant, but that is just the way it is. Just keep looping.

+
+
+

This is a far better way of monitoring multiple items that the previous +because it only uses one server connection per client.

+
+
+

At time of writing (8.80) this is the only API call that maintains state +between calls. All the others require their context to arrive with the +query.

+
+
+
+

Schedules and day categories TODO

+
+

Schedules arrived in 8.40. The API lets you view, create, and delete them, and +modify how they affect their items. It does not let you change the items +they affect.

+
+
+ + + + + +
+
Tip
+
+
+

If you are not familiar with Command Centre:

+
+
+

There are seven types of schedule. They all contain a list of day +categories (more on those later), and for each day category, a list of +times (00:00 through 23:59) paired with an action to perform at that time.

+
+
+

Where the schedule types differ is in the item types they can control, and +the actions that they trigger on the item they are controlling.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Schedule type

Actions

Cardholder

Access granted / denied

Access zone

Zone mode (secure, free, etc.).

Alarm zone

Zone mode (armed, disarmed, etc.).

Output

On / off / cancel overrides

Notifications

On / off

HV/LF

High / low / cancel overrides

Elevator kiosk

Control mode

+
+

The access zone and alarm zone schedules can switch their zone to any mode. +The other five select between two modes.

+
+
+

Some of them have another action that cancels any untimed overrides that +happened since the last scheduled changed, returning control back to the +schedule. It won’t affect overrides that have an end time.

+
+
+
+
+

When updating a schedule you must replace its entire list of day +categories and times. This is quite different from how you update other +lists via this API. The JSON schema has room for us to accept the normal +style as a future enhancement, but in 8.40 you must replace all a +schedule’s schedules at the same time. That suits our target use-case, +which is for an integration to download the schedule, de-serialise it +into an object model, change some part of it, then re-serialise and +upload it back.

+
+
+

As of 8.80, the only types of item that the API can create are +cardholders, visits, and schedules.

+
+
+
+
+
+

Visitor management

+
+
+

Introduction

+
+

The Command Centre Visitor Management feature allows you to manage visit items. A visit contains

+
+
+
    +
  • +

    a list of expected visitor cardholders,

    +
  • +
  • +

    a host cardholder, responsible for a visitor while on site,

    +
  • +
  • +

    a reception, which is a location at which visitors may arrive,

    +
  • +
  • +

    a visitor type, that serves two purposes: an access group that Command Centre will add +your visitors to when you add them to the visit, and an index into more visitor management +configuration,

    +
  • +
  • +

    a list of visitor access groups that your visitors need to be in while on site,

    +
  • +
  • +

    other flat fields such as the dates of the visit and a description.

    +
  • +
+
+
+

The host is person who Command Centre should notify when a visitor +signs in. If a visitor does not have a card, tag boards and reports will +show them in the same access zone as their host. A cardless visitor +appears to follow his or her host around the site, in other words.

+
+
+

The visitor type (an access group) is there so that visitors can have +PDF values assigned before they arrive. This is useful for aids to +identification such as photos and driver’s licence or passport numbers. +Recall that in order to hold a value for a PDF, a cardholder must be in +one of the PDF’s access groups.

+
+
+

The visitor access groups (not the visitor type access group) are +there so that after a visitor signs in they can open doors using a card. +No access groups: no access.

+
+
+
+

Capabilities of the API

+
+

The API lets you read the relevant parts of visitor management +configuration, list receptions, and CRUD visit items, including their +visitors.

+
+
+

It does not let you change visitor management configuration. You do +that on divisions in the Configuration Client.

+
+
+

v8.90 lets you sign visitors in or out and mark them on or off site.

+
+
+
+

Setting up Command Centre to use Visitor Management via the API

+
+

To use the Visitor Management feature, you must:

+
+
+
    +
  • +

    add some visitor management configuration to a division in the Configuration Client, including at +least one visitor type (access group) with at least one host type group and visitor access +group, and

    +
  • +
  • +

    create at least one reception item in the same division, again in the Configuration Client.

    +
  • +
+
+
+

Your software can then use the API to get that division configuration +and the list of receptions. When it creates a visit it will need to +comply to the rules in the division configuration, including:

+
+
+
    +
  • +

    the visit’s visitor type access group must be in the same division as the visit’s reception,

    +
  • +
  • +

    the visit’s visitor type must be one of the division’s visitor types,

    +
  • +
  • +

    the visit’s host must be a member of at least one of that visitor type’s host access groups,

    +
  • +
  • +

    the visit’s visitor access groups must be a subset of that visitor type’s visitor access groups.

    +
  • +
+
+
+

The division in question is the visit’s reception’s division.

+
+
+

Command Centre reevaluates its rules every time you change a visit. We +aimed to provide useful error messages, so if you receive anything +except a 200-level response code please look in the body.

+
+
+

When you add a visitor to a visit, Command Centre will add that +cardholder to the visit’s visitor type access group. Then you can add +PDFs to the cardholder (such as an image). When the cardholder signs in +using a Gallagher Visitor Management Kiosk, or when a greeter signs them +in using the Visitor Management Client, Command Centre will add the +visitor to the visit’s visitor access groups.

+
+
+
+
+
+

The API gateway

+
+
+

The API gateway lets clients connect to the server when they cannot reach its port 8904 directly. +Command Centre needs to be connected to a cloud server, and it needs to turn on the API gateway +globally and in each REST Client item that needs it (which only takes a few clicks) then the client +simply changes the host and port in its connection URL to the cloud server that the Command Centre +server is connected to. That is most likely to be one of these two:

+
+ ++++ + + + + + + + + + + +

Australia

commandcentre-api-au.security.gallagher.cloud

United States

commandcentre-api-us.security.gallagher.cloud

+
+

The port (8904, unless you changed it from the factory setting) also has to go because the gateway +listens on the default port for HTTPS, 443.

+
+
+

Everything else is the same, including the API key, the client certificate, the root path (/api), +and the documentation URL https://gallaghersecurity.github.io.

+
+
+

The request’s source IP number will not be that of your client host by the time the request +arrives at our gateway. It will be the internal address of one of Amazon’s load balancers, so you +will need to untick 'Enable IP Filtering' when you move from the Command Centre server to the API +gateway.

+
+
+

There are no additional licence requirements for the API gateway.

+
+
+

Note that due to the traffic hairpinning through Amazon’s servers, throughput is significantly +reduced.

+
+
+

For a lot more detail about the API gateway, see its technical information paper at +https://gallaghersecurity.github.io/docs/Command%20Centre%20Cloud%20Api%20Gateway%20TIP.pdf

+
+
+
+
+

Notes for penetration testers

+
+
+

This section contains answers to questions raised after penetration tests.

+
+
+

Also see The request process for how the server authenticates and authorises requests.

+
+
+

Item IDs are easily predicted

+
+

All Command Centre’s items are referenced by a URL containing the item’s database ID, which is a +small integer. It is trivially easy to generate the URL ("href") of every item in the system by +starting at ID number one and iterating up. Some tests flag this as an issue.

+
+
+

We expect attackers to do this. We do not rely on a client’s ignorance of server state to protect +it (other than API keys, of course). The server will always enforce the licensing and privilege +models. For example, when a client asks for the details of an item it is unable to view the server +will respond as though the item does not exist (with a 404).

+
+
+

The external penetration testers Gallagher engages have access to our source code so that they can +conduct white-box testing and fully exercise the privilege model. Their reports are available on +request.

+
+
+
+

There is no rate-limiting of authorised requests

+
+

The API does not throttle correctly-authenticated API requests, which makes it look like it is not +protecting itself from denial-of-service attacks.

+
+
+

It protects itself from incorrectly-authenticatd API requests. It raises medium-priority alarms for +the first few bad requests, then another at the highest priority to inform operators that it is +under a DoS, after which it falls silent until the attack ends.

+
+
+

We are looking at options for rate-limiting the API, but it is a balance between protecting +the server from an unlikely situation and preserving the normal operation of our integrations, some +of which make a huge number of requests per second.

+
+
+

We consider a DoS attack from a fully-authenticated bad actor (in other words, one with a valid API +key, TLS certificate, and source IP) is not likely enough to justify urgency.

+
+
+

So far, the denials of service our customers have observed were caused by authenticated integrations +acting improperly. To help developers avoid that the reference documentation contains sections +headed "efficiency tips".

+
+
+
+

The server uses the Host header

+
+

Our web server uses the Host header sent by the client to form URLs that it sends back. Although +the server does not change its behaviour based on the header’s value, using it to build strings is +enough to cause a negative result on some tests.

+
+
+

A client sending a malformed Host header will only affect itself. This would be a problem if an +attacker was modifying (poisoning) a valid request on its way to us, but if the attacker could do +that it would have observed a valid API key and TLS certificate, plus it would be able to pollute +our responses on their way back to the client, so it would hardly bother with this attack.

+
+
+

This class of vulnerability is a problem when there is an HTTP cache between client and server. +This is never the case for program interfaces.

+
+
+
+
+
+

Monitoring the API

+
+
+

The web server presents its vital statistics in Windows performance counters, and (optionally) +writes every API call to a log file.

+
+
+

Performance Counters

+
+

Every API call increments a Windows Performance counter called 'REST API Request Rate'. You can +find it, along with other performance counters updated by Command Centre’s other API servers, in a +category called 'Gallagher Command Centre'. That counter will give you a view of how many calls the +API is receiving.

+
+
+

Per-client performance counters

+
+

If you are running 8.90 or later, another category called 'Gallagher Command Centre REST clients' +contains counters that will show you which of your clients are hitting the API the hardest. They +are called 'Requests per second' and 'Total processing time (ms)' each with an instance for each +REST Client item in Command Centre.

+
+
+ + + + + +
+
Tip
+
+An instance of a performance counter is one created at run-time to suit to the environment. +They are the lowest tier in the hierarchy: category, counter, instance. +
+
+
+

In our case, the environment is a pool of REST clients so CC creates an instance for the REST Client +item the first time an API call arrives for that item (i.e., the first time an API call arrives with +that item’s API key in its Authorization header).

+
+
+ + + + + +
+
Note
+
+The instance takes the name that the client item had when the session was first created, which +is also when all its privileges were calculated. If you rename a REST Client item and notice that +no new performance counter appears, that’s why. Click on the 'Refresh Operator privileges' button +in the item’s configuration page if you need it to update smartly. +
+
+
+

Also note that some characters are not permitted in the name of a performance counter, so the server +may have adjusted them to suit.

+
+
+
+

Per-route performance counters

+
+

The 'Gallagher Command Centre API routes' category, also added to 8.90, will show you which API +routes (a.k.a. endpoints) are the most popular. It contains counters called 'Requests per second' +and 'Total processing time (ms)' again but this time each has one instance for each API route.

+
+
+

Only routes that a client has called will have a counter.

+
+
+

Like client names, API routes have their URLs heavily modified to become suitable performance +counter names and to make them more readable.

+
+
+
+

A word of warning about processing times

+
+

The processing time that comes out of these counters for an API call does not necessarily reflect +the CPU or I/O cost of that call. It is simply the difference in wall-clock times before and after. +If the operation the client requested had to wait for a resource, such as a lock, then the call will +look expensive even though it spent part of its time in a queue.

+
+
+

Therefore these processing times only represent the true cost of an operation when the server and +database were able to give it their full attention.

+
+
+
+
+

Access log

+
+

Servers 8.90 and later write a line to an access log for every API request.

+
+
+

It is controlled by a DWORD value called LogRESTRequests inside the registry key +HKLM\SOFTWARE\WOW6432Node\Gallagher\Command Centre, set to one by the installer. Change its value +to zero to stop the logging. You do not need to restart Command Centre.

+
+
+

9.00 servers and later truncate URLs if they are too long. This is controlled by a DWORD in the +registry called LogRESTURLMaxLength: if a URL has more characters in it than that, it will be +truncated. The default is 200. Zero suppresses URLs altogether. If you want your URLs to go into +the log unmolested, set it to a high value (such as four billion).

+
+
+

The default file name is RESTAccess.log and the file will appear in the same folder as the +server’s licence and other logs. NLog will rotate it once per day by adding a time stamp to its +name. After ten days it will delete it. There is no limit to the size of the file. Those settings +are in the server’s application config, but we advise against changing them as doing so may affect +our ability to offer technical support. Also, your changes may not survive an upgrade. Also, a +syntax error in the app config will prevent Command Centre from starting.

+
+
+

The default file format is JSON, for easy consumption by the popular log aggregation platforms. +Each request will be on one line. Here is an example busted out and indented for easier reading:

+
+
+
+
{
+  "request_time": "2022-12-02T03:41:52.5732150Z",
+  "ip":           "127.0.0.1",
+  "client":       "REST Client item name",
+  "method":       "GET",
+  "url":          "/api/events?top=9999",
+  "status":       200,
+  "elapsed_ms":   502,
+  "endpoint":     "api/events"
+}
+
+
+
+

request_time was the time when the API call arrived, but the log line goes to disk when the call +finishes, so it will not always be an increasing value. A long poll, for example, may be logged 50 +seconds after it was requested, by which time many other (quicker) calls may have started, finished, +and been logged.

+
+
+

endpoint will be null (missing, actually) if the web server couldn’t find a route to service the +request. The status of that will be a 404. Somebody didn’t read about the importance of /api.

+
+
+

The format of this file, plus its name and rotation policy, are yours to configure via the +application config file. There is an example in there of how to produce a format closer to the +Apache common log format. However:

+
+
+ + + + + +
+
Caution
+
+Contact Gallagher Technical Support with your wishes before editing Command Centre’s +configuration file. It is large and complex and if you get it wrong, the server will not start. +Worse, it may start but then not behave as you expect. +
+
+
+
+
+
+

Appendix: Privilege table

+
+
+

This is not the complete list of privileges! See the topic 'Which Operator Privileges +you require' in the Configuration Client’s online help for more.

+
+
+

Remember that privileges lie on divisions, not on items, so when this +table says you need a privilege on some item, take it to mean that you +need that privilege on the division containing that item, or (because +every division inherits the privileges of its parent) one of that +division’s ancestors.

+
+
+

'Advanced User' grants nearly all privileges, so I don’t mention it here.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GoalPrivileges required

View cardholder data at /api/cardholders and /api/cardholders/id except notes and operator +fields.

'View Cardholder' or any of the privileges that allow editing a cardholder, on the cardholder’s +division.

View all cardholder data

'View Cardholder' or any of the cardholder editing privileges on the cardholder’s division, plus +'View Cardholder Notes' reveals notes, and 'View Lockers and Assignments' adds locker detail.

View cardholder PDF values

'View Cardholder' or any of the cardholder editing privileges on the cardholder’s division, plus +your operator group needs to give you 'view' or 'edit' access to the PDF, or the PDF’s default +privilege must be 'view' or 'edit'.

Create cardholders, but not modify them.

'Create Cardholders' on the cardholder’s division.

Create and edit cardholders, except their notes and operator settings.

'Create and Edit Cardholders' on the cardholder’s division.

Edit cardholders, except their notes and operator settings.

'Edit Cardholders' on the cardholder’s division.

Edit cardholder notes.

One of the privileges that lets you edit cardholders as well as either of 'Add Cardholder Notes' or +'Edit Cardholder Notes' on the cardholder’s division. It is different in the thick clients: there, +one of the last two is enough.

Modify cardholder access group memberships.

One of the privileges that lets you edit cardholders on the cardholder’s division plus 'Modify +Access Control' on the group’s division. 'Modify Access Control' on the group’s division is enough +in the thick clients.

Change a cardholder’s location.

'View Cardholder' on the cardholder, and 'Manage Cardholder Location' on the target access zone’s +division, when you are moving the cardholder into an access zone, otherwise any division, when you +are moving the cardholder outside the system. By the way: collecting access zones normally requires +the RESTStatus licence, but there is a variant of that call that returns just the zones your +operator is allowed to move cardholders to that only requires the RESTCardholders licence.

Assign a card to a cardholder.

One of the privileges that lets you edit cardholders ('Create' or 'Create and Edit') on the +cardholder’s division and on the card type’s division.

View assignable card types at /api/card_types/assign

One of the 'Edit Cardholder' privileges on the card type’s division.

View card types at '/api/card_types'

'View Site' or 'Configure Site' on the card type’s division. The privileges that let you create or +edit cardholders also reveal PIV types.

Change locker assignments.

A privilege that lets you edit cardholders on the cardholder’s division plus 'Manage +Locker Assignments' on the locker’s division. 'Manage Locker Assignments' on the locker’s division +is enough in the thick clients.

Disable a card.

One of the privileges that lets you edit cardholders on the cardholder’s division. The 'Disable +Card' privilege has no effect on current versions of the REST API. In the thick clients you do not +need edit privileges on the cardholder if you have 'Disable Card'.

Change a card’s PIN.

"Allow re-printing and re-encoding" or "Print/preview & encode card" or "Edit cardholders" or one +of the privileges that let you edit a cardholder (which in 8.90 are "Edit cardholders" and "create and edit cardholders").

De-authorise a cardholder.

'De-authorise Cardholder' or one of the privileges that lets you edit cardholders on the +cardholder’s division. There is a bug in versions up to and including 8.70 that means you need +'Edit Cardholders' to de-authorise a cardholder. 'De-authorise Cardholder' works on its own in 8.80 +and later.

Delete a cardholder.

'Delete cardholders' on the cardholder’s division. Not 'Create Cardholders', 'Create and Edit + Cardholder', or 'Edit Cardholder'.

Redact a cardholder’s events.

'Delete Cardholder History' on the cardholder’s division.

Redact a cardholder.

'Delete Cardholder History' and 'Delete Cardholders' on the cardholder’s division.

Edit a relationship between cardholders.

One of the privileges that lets you edit cardholders on the cardholder’s division and on the role’s +division.

View a cardholder’s operator configuration

'View Operators' or one of the privileges that let you modify an operator, such as 'Edit Operators' +or 'Enable/Disable Operator'.

View operator groups

'Edit Operator Groups' or 'View Operator Groups'.

Change a cardholder’s operator groups

'Modify Operator Group Membership'.

View PDF definitions at /api/personal_data_fields

'View…' or 'Edit Personal Data Definitions' on the PDF’s division.

View events at /api/events

'View Events and Alarms' or any of the privileges that allow processing alarms, on the division of +the event or alarm.
+Or (in 8.70) 'View Cardholder Events' on the division of the cardholder related to the event and on +the event’s division.

Acknowledge, process, or mark alarms as viewed.

'Edit Alarms' on division of the event or alarm.

Create new events (8.10+)

'Create Events and Alarms' on the division of the source of the event, if the source is not your + REST Client item, or any division otherwise. Prior to 8.90 having it on any division was enough + (bug).

List access groups.

'View Access Groups' or 'Edit Access Groups' on the access group’s division.

List competencies.

'View Site' or 'Edit Site'.

Receive schedule hrefs in an access group

'View Schedules' on the schedule’s division. 'View Site', 'Configure Site', and 'Edit Site' will +not do it.

List access zones and receive their hrefs in other results

'Edit Site', 'View Site', or 'Override' on the access zone’s division.

Override an access zone’s mode.

'Override' on the zone’s division.

List alarm zones

'Edit site', 'View Site', or 'Override' on the alarm zone’s division.

List doors

'Edit site', 'View Site', or 'Override - Open Door' on the door’s division.

Override doors

'Override - Open Door' on the door’s division.

List fence zones

'Edit site', 'View Site', or 'Maintenance Override' on the fence zone’s division.

Override fence zones

'Maintenance override' on the fence zone’s division.

List inputs

'Edit Site', 'View Site', 'Maintenance Override' on the input’s division.

List macros

'View Site', 'Run Macros', or 'Schedule and Run Macros' on the macro’s division.

List outputs

'Edit site', 'View site', or 'Override' on the output’s division.

List day categories

'Configure Site', 'Edit Schedules', 'View Site'. Day categories are divisionless, so having one of +those privs on any division is enough. 'View Schedules' is true to its word: it will not show you +day categories.

List schedules

'View Schedules', 'Edit Schedules', 'Schedule Access Zone' (though the last one only gives you +access to access zone schedules, not the other five types).

Create, edit, and delete schedules.

'Edit Schedules'.

List elevator groups

'Edit Passenger Details' is probably the one you want. 'View Site' lets you see elevator groups, but +might not let you use them on a cardholder.

Set a cardholder’s default floors (for calling elevators)

'Edit Passenger Details'.

Run a macro at '/api/macros/id/run'

'Run Macros' or 'Schedule & Run Macros' on the macro’s division.

Shunt or unshunt an item.

'Maintenance Override' on the item’s division. 'Override', which is good for most other overrides, +is not enough to shunt or unshunt an item.

View a division’s visitor management configuration, and view receptions

'View Site', 'Edit Site', 'View Visits', 'Edit Visits', or 'Manage Receptions'.

View a visit

'View' or 'Edit Visits'. 'Manage Receptions' will not do it.

Modify a visit

'Edit Visits'. 'Manage Receptions' will not do this either.

Modify a division

'Configure Site' or 'Edit Enterprise Data Interfaces' on the division itself.

+
+
+
+

Appendix: Features and licences

+
+
+

Alarms and events

+
+

7.80 allows reading and writing unprocessed alarms. Clients can read all +their fields, mark them as viewed, add comments, acknowledge, and +ultimately process them.

+
+
+

7.80 allows reading events. Clients can see all fields, including these +related items:

+
+
+
    +
  • +

    cardholder, entry/exit zone, division,

    +
  • +
  • +

    (in 8.00) the source item, and the operator and access group on +head-end events (i.e., those that did not come from a controller),

    +
  • +
  • +

    (in 8.30) the door on guard tour events, and

    +
  • +
  • +

    (in 8.40) the item that an operator modified.

    +
  • +
+
+
+

At time of writing (8.30), the following are absent from the list of items related to an +event: locker bank and locker, door (unless it is a guard tour event), +missing competency, car park, and car park space. In practice that is +rarely a problem since those items are often the event’s source, and +will therefore be in the source block.

+
+
+

You need RESTEvents in your licence for all the above.

+
+
+

8.10 allows creating external events with the RESTCreateEvents licence. +Clients can use the usual item types as the source of the event and can +attach one of each of these as related items:

+
+
+
    +
  • +

    cardholders,

    +
  • +
  • +

    operators,

    +
  • +
  • +

    entry access zones,

    +
  • +
  • +

    access groups,

    +
  • +
  • +

    lockers and locker banks, and

    +
  • +
  • +

    doors.

    +
  • +
+
+
+
+

Cardholders and supporting items

+
+

7.90 allows most administrative functions on cardholders, including full +credential maintenance.

+
+
+

It also gives read-only access to supporting items:

+
+
+
    +
  • +

    access groups,

    +
  • +
  • +

    competencies,

    +
  • +
  • +

    card types,

    +
  • +
  • +

    roles,

    +
  • +
  • +

    lockers,

    +
  • +
  • +

    (in 8.10) PDF definitions,

    +
  • +
  • +

    (in 8.50) default elevator floors, and operator privileges.

    +
  • +
+
+
+

8.20 allows moving cardholders between access zones. To support that it +added a call to the access zones controller that requires the +cardholders licence, not RESTStatus as the other access zone calls do.

+
+
+

8.30 allows subscribing to cardholder changes, for integrations that use +Command Centre as a source of users.

+
+
+

8.40 shows an access group’s access zones, Salto items, and privileges +(there are 20). In the Access Group window in the Configuration Client +these are the 'Access', 'Salto Access', and 'Privileges' tabs. These are +all read-only fields.

+
+
+

8.50 adds views of operator groups, receptions, and visitor management +settings, and read-write access to visits and cardholders’ operator and +elevator settings.

+
+
+

8.80 adds cardholder redaction: scrubbing a person’s activity and identifying information out of +the database to help with privacy protection regulations.

+
+
+

Car parks remain on the roadmap.

+
+
+

These cardholder functions require the RESTCardholders licence. 8.20 +added lockers and locker banks to the RESTStatus licence as well, minus +the cardholder information.

+
+
+
+

Non-cardholder items

+
+

8.00 allows read access to basic configuration, status, and all +overrides, to:

+
+
+
    +
  • +

    access zones, alarm zones, and fence zones,

    +
  • +
  • +

    doors,

    +
  • +
  • +

    macros, and

    +
  • +
  • +

    outputs and (in 8.10) inputs.

    +
  • +
+
+
+

You will need the RESTStatus licence for the GETs and RESTOverrides for +the override POSTs. 8.60 added the GETs to RESTOverrides, but in a +slightly limited way: they will search for items to override, but they +cannot return their status.

+
+
+

8.20 added lockers, previously only visible with the RESTCardholders +licence, to the RESTStatus licence.

+
+
+

8.30 added the ability to monitor more than one item per connection.

+
+
+

8.50 allows read access to day categories and read-write access to +schedules. You can manage the day categories and times on a schedule, +but not which items it affects.

+
+
+

Day categories are divisionless, which led to a slight change in the +/items controller: divisionless items did not appear there before +8.50; now they do (subject to privilege checks, of course).

+
+
+

8.50 allows viewing elevator groups.

+
+
+

As of 8.60, cardholders, schedules, and visits are the only items you +can modify via the API.

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/training/rest_training.adoc b/training/rest_training.adoc new file mode 100644 index 0000000..3569cfe --- /dev/null +++ b/training/rest_training.adoc @@ -0,0 +1,4032 @@ += Command Centre REST API training material +:toc: left +:source-highlighter: rouge +// highlighters are coderay highlight.js Pygments rouge. Asciidoctor ships with highlight.js, but +// gihub pages (Jekyll) uses Rouge. Install it with 'gem install rouge'. + +// Practically all examples are json +:source-language: json-doc + +// Section numbers are handy for things like "read sections 4 and 20" but then one day 20 +// becomes 21. So, leave section numbers off: +// :sectnums: + +// Shortens image URLs: +:imagesdir: assets + +// Need anchors or links, but not both, and links are less visually distracting (IMO). +// :sectanchors: +:sectlinks: + + +// Editors, please stick to a maximum line length of 100. + +[.lead] +WORK IN PROGRESS. + +This document is an AsciiDoc port of a document used internally at Gallagher Group to train +developers in the use of Command Centre’s REST API. This version is not yet suitable for +publication, but if you are using the REST API, and like the sound of 60-odd more pages of reading +material, and are tolerant of casual language and a choppy structure meant to help a live speaker +keep the attention of a room of developers, please continue. + +Also, please contact Gallagher through your channel partner and talk to our Technical Support +Engineers about your project, because we are eager to help you make your integration the best it +can be. + +// float: not in the section hierarchy, and so not in the table of contents +[float] +== Disclaimer + +This document gives certain information about products and/or services +provided by Gallagher Group Limited or its related companies (referred +to as "Gallagher Group"). + +The information is indicative only and is subject to change without +notice meaning it may be out of date at any given time. Although every +commercially reasonable effort has been taken to ensure the quality and +accuracy of the information, Gallagher Group makes no representation as +to its accuracy or completeness and it should not be relied on as such. +To the extent permitted by law, all express or implied, or other +representations or warranties in relation to the information are +expressly excluded. + +Neither Gallagher Group nor any of its directors, employees or other +representatives shall be responsible for any loss that you may incur, +either directly or indirectly, arising from any use or decisions based +on the information provided. + +Except where stated otherwise, the information is subject to copyright +owned by Gallagher Group and you may not sell it without permission. +Gallagher Group is the owner of all trademarks reproduced in this +information. All trademarks which are not the property of Gallagher +Group, are acknowledged. + +Copyright © Gallagher Group Ltd 2021. All rights reserved. + +Gallagher Group Limited + +PO Box 3026 + +Hamilton + +New Zealand + ++64 (7) 838 9800 + +E-Mail: sales.nz@security.gallagher.com + +Website: www.gallagher.com + +== Introduction + +[.lead] +DRAFT DO NOT DISTRIBUTE. + +This document is an introduction to using the REST API in Command +Centre, aimed at those involved in the development of software that will +integrate Command Centre into other solutions. It was written to +accompany an informal education session with a Gallagher trainer. + +It covers features first released in 7.80 and expanded in 7.90. + +It uses the following styles for guided examples: + +.Sample REST query and the resulting JSON +[source] +---- +// GET /api <1> +{ + "a text field": "string", // including comments + "a numeric field": 1234, + "a Boolean field": false +} +---- +<1> Strictly speaking, JSON does not contain comments. + + +`This style` indicates filenames, URLs, and text that benefits from vertical alignment. + +.... +Fixed-width blocks are client requests that you can copy out for your own work, +and server responses, pretty-printed a little to make them readable. +.... + + +=== Exclusions + +This document does not cover special handling of PIV cards. It shows how +to create a generic card and leaves the variations for PIV and PIV-I to +the developer documentation. + +Nor does it cover some of the features added after v7.90: access zones, +alarm zones, fence zones, doors, outputs, inputs, PDF definitions, +and macros. Moving cardholder between access zones, operators, visitors, +schedules, and elevator groups. Subscribing to cardholder updates, an +efficient way of monitoring large numbers of items, and PII redactions. + +== Start here + +Do you want to learn the bare minimum about Command Centre (Gallagher's access control product) to +get you started on an integration? Read <<_useful_background>>. + +Do you want to set up Command Centre and try out its API from a REST client? Read +<<_training_setup>>. + +Do you want to learn something about HTTP queries in general? <<_http_requests>> will help. + +Do you need an introduction to how HTTPS uses certificates? +<<_authentication_and_encryption_certificates>> is for you. + +Do you want to use client-side certificates to authenticate your client? Very wise. +<<_client_side_certificates>> has what you need. + +Are you more concerned about the security of the API? Look in <<_the_request_process>> for how the +server authenticates and authorises requests in general, <<_client_side_certificates>> for how it +can use certificates to authenticate clients, <<_notes_for_penetration_testers>> for a mini-FAQ +on pen test findings, and <<_the_api_gateway>> for deep technical information on the API gateway in the cloud. + +== References + +This document refers to API documentation on github and the online help and sample code +on the Command Centre ISO (or DVD, if you have physical media). + +=== Developer API documentation + +https://gallaghersecurity.github.io/ holds the reference API +documentation. That is the primary reference for the REST API, so it +aims to be complete, and you should have it on hand whenever developing +against Command Centre. However the amount of detail can be daunting and +it is not very introductory, which is why this document exists. + +That reference documentation is in four sections: + +* `cardholders.html` describes the cardholder API calls and supporting concepts, such as card types, +access groups, PDFs, roles, and competencies. These functions were new to 7.90. 8.30 added a +cardholder change-tracking API; + +* `piv.html` covers the additional fields you supply and see on PIV and PIV-I cards; + +* `events.html` covers the alarms and events calls. This is all that was available in 7.80. 8.10 added +the ability to create your own events; + +* `rest.html` covers Command Centre items that are not cardholders, alarms, or events: alarm zones, + access zones, fence zones, outputs, doors, and macros arrived in 8.00, and inputs in 8.10. 8.30 + added a way to mass-monitor items. Schedules and elevator groups arrived later still. + +We are always improving the content so it is best read online, but if you need an offline copy you +can download a ZIP from https://github.com/GallagherSecurity/cc-rest-docs. The documentation is no +longer on the Command Centre install media. + +If running on Windows, something in the mix of Internet Explorer, +Javascript, and `file:` URLs on network shares prevents the HTML rendering +properly so if those files look goofy to you, try a different browser or +copy the folder to your local drive. Or read it online. + +=== API gateway technical paper + +If you are thinking of using <<_the_api_gateway>> this document will get you started, but if you are +after a deeper understanding: + +https://gallaghersecurity.github.io/docs/Command%20Centre%20Cloud%20Api%20Gateway%20TIP.pdf + +=== On the DVD + +The Configuration Client’s Help menu opens a CHM file that you can also +find in the ISO at +`Setup\Program Files\Gallagher\Command Centre\Client\Resources\en` +or `Setup\Program Files\Gallagher\Command Centre\Bin\Resources\en`. There +is a PDF version, split into three volumes, on the ISO in the +Documentation folder. + +The Command Centre hardening guide, also on the ISO, is required reading +for security-conscious sites. While you may not be able to follow its +leading advice regarding the REST API ("leave it turned off") there is +plenty more in there to be aware of. + +=== Sample code + +See `Utilities/REST API/REST API Sample Code.zip` in the Command Centre +ISO. There is a WPF client in there and a console application in a C# +Visual Studio solution. + +=== Abbreviations + +AWS:: Amazon's cloud. + +CC:: Command Centre, Gallagher's access control product. + +PDF:: Personal Data Field. Not Adobe’s kind. In Gallagher’s defence, these PDFs predate +Adobe’s. + +PII:: Personal Identifiable Information. Any information in Command Centre's database concerning a +person, including names, PDFs, movement events, group memberships, roles, competencies, etc. + +== Training setup + +If you wish to try the REST API for yourself, you will require: + +* Command Centre 7.90 or later with a RESTCardholders licence, a RESTEvents licence if you are to + examine events, RESTStatus if you are to look at site items, RESTOverrides if you want to override + them, and RESTCreateEvents if you wish to create events. This document does not cover the last + three. + +* A host capable of reaching port 8904 on Command Centre via HTTPS, or access to the desktop of the + CC server itself. + +* (Recommended) the sample REST client application from the Command Centre installation media (8.10 + onward). + +* (Optional) the https://postman.com[Postman] installer, or access to it on the internet. Any REST + client will do, but this document shows how to set up Postman. + +* (Optional) Chrome and access to the internet for two extensions. Chrome can be easier to use than + Postman, in some cases. Again, any web browser will do, but this document shows how to set up + Chrome. + +* (Optional) wget or curl, two command-line utilities commonly found on non-Windows systems. + +* The link:../ref[API developer documentation]. + +== Useful background + +This section contains material you should have aboard before reading on. +Skip it if you are familiar with CC. + +=== Cardholders + +Cardholders are user accounts. Depending on what you give a cardholder +account it can suit different purposes: + +* people with cards and access needs, but no administrative responsibilities. The REST API allows +management of these kinds of cardholders; +* administrative people (operators) with all that plus the rights to configure the system and manage its +users. The 8.50 API was the first with features for managing these kinds of cardholders; +* system accounts with no person associated and no physical access, but administrative access to the +system. You are about to create one of these. + +=== Operators and operator groups + +Operators are cardholders with benefits. Cardholders become operators through +membership of one or more operator groups. An operator group bestows +privileges on its members, including the ability to log in to the +Command Centre thick clients or run REST queries. + +Operator groups have no effect on access control, so they do not appear in this document again +except when link:#_create_a_rest_operator[creating an operator] which puts a cardholder in an +operator group while setting up a REST client. Operator groups came to the API in 8.50. + +=== Access groups + +Cardholders can be members of any number of _access groups_. An access +group can be a member of one other: its parent. Command Centre considers +a member of a group to be a member of all the groups up its parenting +line, as you would expect. + +A cardholder must be a member of an access group before he or she can +open a door, so every cardholder that represents a person should have +group memberships. (Footnote: there are exceptions of course. Some +visitors, for example, do not need to open doors, but they exist in CC +so that it can record their location as they move around the site with +an escort opening doors for them.) + +A cardholder can have many memberships of the same group. This is useful +because each has its own start and end times. Past memberships fade +away. + +Access groups are not operator groups. When this document refers to a +group it means an access group. + +A cardholder must be a member of an access group before he or she can +have personal data, next. + +=== Personal data fields (PDFs) + +A Personal Data Field adds a custom value to a +cardholder. Each PDF has a type (text, image, numeric, date, telephone +number, email address, …) and optional constraints on the values that it +can hold. For example, text, email, and telephone number types can have +a regular expression attached which a new value must match before +Command Centre will accept it. A date can have a maximum and a minimum. +Text PDFs can have a list of valid values, like an enumeration. + +There is more configuration: image PDFs have a type and size, to which +Command Centre will transcode incoming images. Mobile numbers and email +addresses have a flag indicating whether they are suitable to receive +SMS and email notifications. 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 (Footnote: direct or inherited. Unless otherwise noted, all +Command Centre’s access group membership tests treat inherited members +just like direct members). + +The REST API allows you to manage a cardholder’s group memberships (so +that he or she has the PDF) as well as see and set PDF values. It does +not let you change the configuration of the PDF itself. + +Whenever this file or the API's reference documentation uses the term 'PDF' it means a personal data +field. + +=== Divisions + +Every item in the API--we will get to items in a moment--is in a _division_ (footnote: except day +categories. They are divisionless). Divisions are arranged in a tree: each has exactly one parent, +aside from the root division, which has none. An operator group specifies the roots of the division +trees to which it grants privileges. + +Therefore an operator with privileges on the root division has those +privileges on all that server’s objects. + +Complication: multi-server clusters have one root node (and therefore +one tree of divisions) per server. + +If you find that an operator cannot see or modify an item, the questions +you should ask are: + +==== Which division is the item in? + +The Command Centre client shows a cardholder’s division in the +'Cardholder Details' pane of the cardholder viewer. The Configuration +Client shows the division of any item in the 'General' tab of its +property page. The REST API shows it in the 'division' field. + +IMPORTANT: *The operator’s division and his or her operator groups' divisions in the 'General' tabs +are irrelevant* to privileges. The operator group grants privileges on the divisions in the +'Divisions' tab. + +There is a small section on link:#_operator_privileges[operator privileges] below. + +=== Access zones + +An _access zone_ represents a physical area with Gallagher-controlled doors +on its perimeter. Something like a room. + +An access zone can be open or secure. If open, all its doors are unlocked, +but when it is secure the doors are locked and cardholders attempting entry +will be subject to an access check. + +Since a real door has a space on each side of it a Command Centre door can +have two zones attached: an _entry_ zone and an _exit_ zone. The only +difference between the two is the event that Command Centre creates when +someone badges from one to the other: 'access granted' or 'exit granted'. + +No matter which way around the zones are attached to a door, when a +cardholder moves through it the resulting event calls the the zone into +which they moved the 'entry' zone, and the one they just left the 'exit' +zone. + +=== Roles + +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. It makes more sense when you use the example +'supervisor': a person has a supervisor and is a supervisor for many +others. 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 you can change the cardholder’s supervisor, but to change +who the cardholder supervises you need to edit those individuals. + +=== Competencies + +Basically, _competencies_ are another condition that a cardholder must +meet to pass an access check at a door. + +The REST API lets you manage the links between cardholders and +competencies: create them, delete them, enable/disable them, and set +their expiry dates. + +You might like to sit down for this part. +A competency can be disabled, expired, both, or neither. Actions at a +door can depend on whether a competency is disabled, expired, soon to +expire, or all good. + +Whether it is enabled is a flag, plain and simple. Whether it is +expired is derived from an expiry timestamp: if it is +in the past, Command Centre considers the competency expired. + +A competency can also have an enable date. If that date (timestamp) +passes while the competency is disabled, Command Centre will enable it. + +If the competency is not disabled, the 'expires' time is important. If +it is in the past, the cardholder’s competency is expired. If it is not +set, or it is in the future, the cardholder benefits from the +competency. + +A cardholder can have only one link to each competency. They differ from access groups in that way. + +[cols=",,,",options="header",] +|=== +|Enabled flag |Enablement date |Expiry date |Status +|Set |- |Far future |Active +|Set |- |Near future |Active (with a warning at the door) +|Set |- |Past |Inactive (expired) +|Unset |Future |- |Inactive (pending) +|Unset |Past |- |Inactive (disabled) +|Unset |null |- |Inactive (disabled) +|=== + +=== Card types + +A card type carries rules for the data that a card carries, PINs, how to +treat cards around their expiry time, and default values for new cards +of that type. We often use the word "credential" because not all card +types involve a physical card: there are also biometric and mobile card +types. + +The REST API provides read access to card types so that you can manage +cardholders’ credentials. + +PIV cards have their own developer document, separate from the rest of +the cardholder API. + +=== All the above are items + +The API lets you search for items and examine them, but--other than +cardholders and schedules--it does not let create, alter, or delete them. +The purpose of the cardholder API is to let you associate items with +cardholders and manage those associations. + +=== Items versus a cardholder’s link to them + +Talking about a PDF or a competency can be confusing, because there is a +PDF item and a competency item, and cardholders can have PDFs and +competencies, but the item and the cardholder’s link to the item are +different things. + +The items (on the left in the table below) and the connection to a +cardholder (on the right) both appear in the REST API, but the API only +lets you change the things in the right column, the connections. +So let +us make some definitions: + +[width="100%",cols="50%,50%",options="header",] +|=== +|Item |A cardholder’s possession of that item +|Access group |Group membership + +|Competency |Cardholder competency + +|Role |Relationship + +(the role is the nature of the relationship between two cardholders) + +|Locker |Locker assignment + +|PDF |Cardholder PDF + +or + +PDF value +|=== + +There is a question of scale. You may have only two competencies, but thousands of cardholders with +those competencies. You may have only one role in the system, but every one of your cardholders +might have a relationship using that role. + +For that reason, the API calls that list items such as roles and competencies do not list their +connections to cardholders. The result sets would be too large. Instead, +you see those connections from the cardholder side: when you GET a +cardholder’s details, you will see all the connections that cardholder +has to PDFs, competencies, groups, cards, lockers, and roles. + +Access groups and operator groups will show you their cardholder +members, but only if you ask. + +=== Events and alarms + +Events record occurrences in the system. They are not items. They have +an ID, a source item, an occurrence time, and links to other related +items. Events are immutable: the events you read from the REST API will +not change. + +Alarms are events with extra fields, and some of them are mutable: there +is a free-text notes field that you can edit in the thick clients, a +history, and Booleans recording whether the alarm is acknowledged, +processed, and active. + +The alarms interface only shows unprocessed alarms in its search +results. Once an operator processes an alarm, it disappears from alarm +searches. However, the alarm still exists in the database and an alarm +is also an event, so the events interface will return it whether +somebody processed it or not. + +// Don't change the name. It's linked. +=== Operator privileges + +Or just 'privileges' since there is no other kind. + +An operator has privileges over a division and all its subdivisions. +When we refer to an operator having a privilege on a cardholder, for +example, we mean that the operator has that privilege on the +cardholder’s division, or one of its ancestor divisions. + +In the interests of security, you should give your REST operators +(footnote: _all_ operators) the minimum privileges they require to +achieve their task. + +<<_appendix_features_and_licences>> gives examples of privileges you will need for various tasks. + +=== HTTP requests + +An HTTP request has four parts: a verb, an address, a handful of +headers, and a body. + +==== Verbs + +The verbs we will use are GET, POST, PATCH, and DELETE (in upper case by +convention). GET and DELETE are self-explanatory but the other two are +often confused. In this API we use POST to create something new such as +a cardholder, and PATCH to modify something like the end-date on a group +membership. + +==== Addresses (URLs) + +The address is the URL that everyone is accustomed to. In a REST API the +address identifies the object you wish to GET, PATCH, or DELETE. When +POSTing, the address identifies the type of thing you wish to create. + +==== Headers + +Headers are a list of key/value pairs. We use one called Authorization +(spelled with a Z) to carry client authentication, and one called +Content-Type to be clear that we use JSON. + +==== Bodies + +The body of a GET or DELETE request is empty. A POST can also be empty, +but they usually carry some instructions for what you want created. A +PATCH always needs a body that contains instructions for how to modify +the item identified by the address. + +If the body is not empty, it must contain JSON (below). + +The sample application "CCFT REST Client" opens a console window that +shows you the verb and address of the HTTP queries it is making. It can +also show you the bodies of its queries and the server’s responses. + +=== HTTP responses: codes, and more headers and bodies. + +An HTTP response has three parts: a numeric response code, more headers, +and a body. + +==== Response codes + +Any response in the 200-299 range means success. GETs return a 200 along +with their results. DELETEs and MODIFYs return 204 ("no content"), +which just means they succeeded and having nothing more to say. Creating +a cardholder or event returns 201 ("created"). + +A response in the 400-499 range generally means there was something +wrong with the request. One exception is 409: it could mean that your +timing was bad and trying again later may succeed. Along with 403 and 404, 409 +could also mean you have attempted to do something beyond your +privilege. 401 means you did not sent a good Authorization header and +the server does not trust you at all. +The body of the response that comes back from the server will +tell you the problem. + +500-level responses mean the server has met with trouble. Waiting for +updates on events or items is an exception: if you ask Command Centre +for updates and none arrives before the timeout, it will return 503. +That is actually a kind of success: it means nothing changed while you +were waiting, so a future version of Command Centre may return a +200-level code in this case. + +==== Headers + +The only time Command Centre returns a header of interest is after it +processes a POST to create a cardholder or event. It sets a header +called Location containing the URL of your new object. + +==== Bodies + +The body of a GET response contains everything you asked for, in JSON. +The body that comes back from other verbs is empty unless there was a +problem. + +Chrome will show you the body. Press F12 and resend the request to make +Chrome show you the response code and the headers as well (along with +lots of other useful information). Postman (a web client we will get to +later) always shows you everything. + +=== JSON + +...though familiarity with XML or any programming language should be +enough. With line breaks and indentation and a bit of colour, JSON is +quite readable. + +JSON can contain flat fields, objects (structures), and arrays. + +[source] +---- +{ + "a text field": "string", + "a numeric field": 1234, + "a Boolean field": false, + "an object": { + "sub-field1": "foo", + "sub-field2": "bar" + }, + "an array": [ + { + "sub-field1": "jingle", + "sub-field2": "bells" + }, + { + "sub-field1": "foo", + "sub-field2": "bar" + } + ] +} +---- + +In that example, the array called `an array` shows an array containing two +more objects, each of which contains two fields of its own. + +=== Authentication and encryption certificates + +Before an API call can succeed the client needs to decide to trust the +server and then the server needs to decide to trust the client. They do +that using _certificates_. + +First a little background. Very simply put, the current algorithms for +secure communication require a pair of _keys_. Keys are nothing more +than huge numbers. The two in the pair are different from each other, +but mathematically related so that when you encrypt some data using one +key, nobody can decrypt it if they do not have the other key. The key used for +encryption is public because there is no harm in encrypting data. +People toss those keys around like business cards. Its mate, however, +is very, very private, because it is the one that unlocks the secret message. + +These keys do more than just encrypt +and decrypt data so we do not use those terms in their names. The business +card one is called the _public key_ and the other is +the _private key_. + +A _certificate_ contains a public key plus metadata: what the key is +meant for, how long it is good for, and some proof that it is authentic, +if there is any. That proof of authenticity takes the form of a +signature from an authority that the internet has agreed to trust, such +as Symantec or Verizon. Client certificates, and some server +certificates, do not have a signature. Or they do, but it is their own +signature, which does not really count because nobody trusts it. Such +certificates are called _self-signed_. Web browsers cook up their own +self-signed certificates all the time. Web server certificates, on the +other hand, last for months or years. + +If an HTTPS client and server connect and establish an encrypted channel +of communication without checking certificates, it will be secret (nobody +will be able to listen in) +but they should not trust each other. The other end could be +fibbing. So they conduct a negotiation to establish each other’s +identity (i.e., they _authenticate_). + +Usually a web client requires proof of authenticity from the server, +since you want to be sure that it really is your bank’s web site you are +looking at and not a fake. If the server does not provide that, the +client shows a warning. When working with the Command Centre API you +will have to link:#_ignore_server_certificate_warnings[work around it in +Chrome] and +link:#_never_mind_that_your_server_certificate_is_self_signed[work around +it in Postman]. If you want to install your own server key, the topic +'Changing the Web Services' in the Configuration Client’s online help +shows you how. + +Sometimes the server also requires a proof of identity from the client. +This does not happen when using most web sites because (continuing the +bank example) your bank does not care where you are coming from. +It uses your password to authenticate you. But APIs should operate more +securely than web sites so our recommendation is to turn on the feature +that makes the server check your clients' certificates. +<<_client_side_certificates>> covers client +certificates. + + +IMPORTANT: *Know the difference between the two certificate checks, and that they are completely +independent*. If the client drops a connection because it does not trust the server, Command Centre +cannot raise any alarms, because it never received a proper connection. The problem is on the client +and there is nothing you can do to Command Centre to help. But if the server certificate is +acceptable to the client, the server has a chance to check the client certificate. If the server +does not like the client certificate, Command Centre will raise an 'invalid client certificate' +alarm. + + +== Enable Command Centre's API + +=== Turn on the web server + +Configuration client -> File -> Server Properties -> Web Services (about 15 down). + +Enable the REST API and–-for the moment–-tick the checkbox to the right of the port. This is an old +screenshot: it changed labels in 8.50. Have a good look at the status because if your server has a +problem binding a socket (which just means "listening"), it will show here first. + +image::server_props_turnon.png[Enabling the public API,title="Enabling the web server"] + +The Configuration Client’s online help covers this in the topic called +'Web Services'. + +WARNING: ⚠ *Make sure 'Do not require pinned client certificates' is off in production*. In 8.50 it +changed its name to 'Enable REST Clients with no client certificate'. It ships turned off: make sure +it stays off on production servers. + +Requiring pre-shared certificates from clients is the best protection +the server has against attackers on its network. If you tick the box to +turn off that check when you first start your development, come back +once your application is connecting successfully and untick it again. +Read <<_client_side_certificates>> to help get your application connecting again +after doing that. + +=== Installing a custom server certificate + +You do not need to install a custom server certificate for experimental development. If you +eventually choose to do it, it all happens under a button that arrived in Command Centre after I +took the screenshot above, labelled 'Manage Certificates'. The Configuration Client’s online help +covers it in detail in a section called 'Replacing the web service certificate' in the 'Changing the +Web Services' topic. You can either import a public/private key pair into Command Centre (which is +simple, secure, and recommended) or use the Windows Certificate Store (which uses Microsoft’s +security instead of Command Centre’s). The summary of the Certificate Store process is: you need to +name your certificate 'Gallagher Command Centre Server' (please take care with the spelling), place +it in the 'Gallagher Applications / Certificates' folder of the Local Computer Certificate Store, +and give Command Centre the rights to use it. The online help lays that out step by step. + +=== Create a REST operator + +We will get to the reasons why in <<_why_we_need_an_operator>>. + +==== Create an operator group and give it the necessary privileges + +You can do this in either the operational client ("Command Centre") or the configuration client. + +Give the group the lowest level privileges it needs. For this exercise, +you will need 'Create and Edit Cardholders' and 'Edit Alarms'. 'Modify +Access Control' and 'View Site' could be handy later. + +image::op_group_privs.png[Adding privs to an operator group, title="Adding privs to an op group"] + +WARNING: Not 'Advanced User'. Never 'Advanced User'. + +See link:#_appendix_privilege_table[this appendix] for a table of +privileges an operator needs for common tasks. + +One group is enough for experimenting but when it comes to production, +create an operator group for each class of client you have connecting +and give each group different privileges. An operator can be in more +than one operator group; use this flexibility as you need. + +==== Create a cardholder and add it to the operator group + +You can do this in either of the clients. + +image::op_group_members.png[Adding cardholders to an op group,title="Adding cardholders to an op group"] + +In production, your operator should have a bare minimum of +capabilities, so do not give it a card, logon, password, or user code. +Do give it plenty of description about what it does, where it connects +from, and who to contact about it, because the people running the +security system will not be the people who run your software +integrations and they will need all the help you can give them when +problems arise. + +During development it helps to log in to the Command Centre clients +sometimes, so I give the REST operator a logon, password, and the +'Launch Configuration Client' privilege. + +Now that you have an operator, you need to let the REST API use it. + +=== Create a REST Client item + +…(in the server) and assign an operator. + +We call it a 'REST Client' but it is really a mapping from an API key +to an operator. More on this later. + +Using the Configuration client, Configure -> Services and Workstations +(at the bottom). Right-click menu -> New -> REST{nbsp}Client. + +Set a name, then go to the 'API Key' tab. + +Drag your new operator (Manage -> Cardholders) into the 'REST Client +Operator' box. That box looks like it can hold more than one: it cannot. + +Take a note of the API key. You will need it for your clients (the +sample app, Chrome, or Postman). + +image::rest_client_api_key.png[A REST Client item's API key in the Configuration Client,title="A REST Client item's API key in the Configuration Client"] + +IP filtering is a layer of security that makes it that much harder for +an attacker to attack your server. + +image::rest_client_ip_filtering.png[A REST Client's IP filters in the Configuration Client,title="A REST Client's IP filters in the Configuration Client"] + +(A space is as good as a comma.) + +=== What is an API key? + +Your client sends this to Command Centre with every request. It is the +username and password combined. Take care of it. If someone steals your +API key and you have not taken other precautions (client certificates +and IP filtering) they could masquerade as you. + +If something makes an API call without an API key, or with an API key +that Command Centre cannot find on one of the REST Client items, CC will +raise an error 'A REST connection was attempted with an invalid API +key'. + +When a client sends it to the server in an HTTP header it prepends +`GGL-API-KEY` and a space. That string is not part of the key and you +should not use it in any of the places that expect an API key. It is +just there so the HTTP request conforms to an Internet standard. + +== Try the sample client application + +This section needs fleshing out with proper prose, but until that +happens the major points to cover are: + +The sample client is the quickest way to make sure CC is working +properly. For Windows users, it is better than a web browser (which +requires plugins and hides error messages) or Postman (which is fiddly +if the server is checking client certificates). + +Find the sample client on the installation media under Utilities / REST +API / RESTClient_version. It has been there since 8.00. + +It is not a Command Centre management application! It is a library of +sample code for developers, which happens to compile and run. You can +check the status of many items, override most of them, watch and create +events, and create, look up, and move cardholders, but some features +like lockers and car parks are missing. + +Watch the multi-coloured console to find out what URLs to use in your +own requests. Later versions include options on the login screen to also +show the JSON that the client is sending and receiving. + +The source code for the demo app and a few others is on the installation +media. + +== Set up Chrome + +If the sample GUI app works and you want to see the data that comes from +Command Centre, a web browser is all you need. If you also want to +create and change items you should skip this section and install +Postman. + +There are two extensions you need to install for Chrome to be really +useful. One sends the API key to the server, and the other dresses up +the JSON that it sends back. + +NOTE: Since writing this, several browser extensions have appeared that test REST APIs quite +thoroughly. They may provide a better experience. Shop around. + +=== Install the ModHeader extension + +You need to set a custom header, because that is how we send the API key +and without that Command Centre will give you nothing. + +Start by clicking the 'Modify Headers' icon in Chrome. (Footnote: +confusingly, there is also an extension called 'Modify Headers', which +is different from 'ModHeader'. Use either.) + +Set a header called Authorization with a value of `GGL-API-KEY` followed +by a space and the API key you took from the configuration client. Note +in the example below I have two headers ready to go, only one of which +is active. They are too wide for the Modify Headers window (there are +three more characters). + +In 7.90, both must be in upper case. + + +WARNING: ⚠ *Set a filter so that the header only goes to your Command Centre +server. Otherwise Facebook will have your API key*. + + +Use a URL pattern in the filter that all your queries will match but +other web browsing will not. ModHeader now uses regular expressions, so +if you have dots in your hostname you must put backslashes in front, +`\.`. + +image::chrome_mod_header_setup.png[Configure Mod Header Chrome extension,title="Configure Mod Header Chrome extension"] + +=== Install a JSON viewer + +Raw JSON straight from the server contains no whitespace, so it is not +that easy to read. There are a few Chrome extensions that pretty-print +JSON for you. I use 'Awesome JSON Viewer' because it is recent (April +2020) and can collapse and count sub-items. It is rebranding itself +'JSON Viewer Pro', so you might try searching for that. Despite having +'pro' in the name it remains free. + +=== Ignore server certificate warnings + +Send Chrome to `https://yourhost:yourport/`. `yourport` will be 8904 unless you changed it when +you link:#_turn_on_the_web_server[set up the web server]. If your server does not have a certificate +with a trust path to a trusted root certificate, you need to click through the warning below. It +will reappear occasionally. You can turn it off in Chrome but it is not a good idea, since you want +to know when other servers are using self-signed certificates. + +image::chrome_bad_server_cert_1.png[Chrome fretting about a server cert,title="Chrome fretting about a server cert"] + +image::chrome_bad_server_cert_2.png[Chrome fretting in more detail,title="Chrome fretting in more detail"]] + +== Set up Postman + +If you want to do more than look, you need Postman, because Chrome does +not let you POST, PATCH, or DELETE as easily as Postman does. + +Postman used to be a Chrome extension but is now a standalone +application. Both work. These screenshots are from the application. + +=== Send the API header with every request + +This is what the Modify Headers extension does in Chrome. It makes +Postman send an Authorization header containing your API key with every +request. + +Your requests also need a Content-Type header but you do not need to set +it yourself. Postman will add that after the next step. + +image::postman_auth_header.png[Setting auth header in Postman,title="Setting auth header in Postman"] + +*There is a mistake in that screenshot*: the value for the Authorization +header should have `GGL-API-KEY` and a space before the API key. Later versions of Command Centre +will not work without it. + +// ...is the plan. + +=== Set the content type to JSON + +Otherwise Command Centre will reject it as invalid. + +image::postman_content_type.png[Content-type Postman header,title="Content-type Postman header"] + +You must use `application/json`, in lower case, nothing more. People +have tried adding a semicolon and `charset=utf8`, but that just stops +all queries from working. + +=== Never mind that your server certificate is self-signed + +In the current version of Postman, the settings are behind the cog in +the top tool bar, not the sliders in the environment toolbar below it. + +For older versions of Postman, the settings are behind the open-ended +wrench in the top tool bar, not the cog in the environment toolbar below +it. + +image::postman_server_cert_warning_off_1.png[Postman settings menu,title="Postman settings menu"] + +Pick 'Settings' and turn off SSL certificate verification. Turn off the +other options if you want to keep it looking clean. It makes no +difference to Command Centre. + +image::postman_server_cert_warning_off_2.png[Postman SSL cert verification off,title="Postman SSL cert verification off"] + +== First GETs: cardholders + +=== The most basic GET + +Using Chrome, go to https://yourserver:8904/api again. This document +and the developer documentation use the following shorthand for that instruction, which omits +the protocol, host, and port: + +.... +GET /api +.... + +Doing that will test everything you have set up so far. If it did not +work, look at the error message in the response body (Chrome will show +it) and the most recent events in Command Centre. + +If it did accept your API key, the only thing that can stop you now is a +licensing problem: + +---- +{ + "message": "feature not licensed" +} +---- + +With a RESTEvents licence you will get more: + +---- +{ + "version": "7.90.0.0", + "features": { + "items": {...}, + "alarms": {...}, + "events": {...} + } +} +---- + +With a RESTCardholdersEvents licence: +---- +{ + "version": "7.90.0.0", + "features": { + "items": {...}, + "alarms": {...}, + "cardholders": {...}, + "events": {...}, + "accessGroups": {...}, + "roles": {...}, + "lockerBanks": {...}, + "competencies": {...}, + "cardTypes": {...} + } +} +---- + +That is not the exact JSON you will get (that is not even JSON) but hopefully +you get the idea. + +=== Cardholder summary + +.... +GET /api/cardholders +.... + +That means you should Chrome or Postman to `https://yourhost:yourport/api/cardholders`. + +Your operator should be there. Try following some of the links. If you +are using Chrome, just click on them. + +One of the links called `href` (probably the first one) will take you to a cardholder's _details_ +page, covered in the next section. + +Next try: + +.... +GET /api/cardholders?top=1 +.... + +That limits the results to one cardholder. If you don’t have a `next` +link in the result, it will be because there is only one cardholder in +your system or your operator only has access to one. + +Now apply the advice from the efficiency section of the developer +documentation for collecting a lot of cardholders at once: + +.... +GET /api/cardholders?sort=id&top=10000 +.... + +v8.00 delivered the ability to add all the fields from the details page +to the summary page, using the `fields` parameter. See the developer +documentation for a proper description, but in short, try adding +`fields=_fieldname_` to your request URL (after a `?` or `&` of course) +where _fieldname_ is the name of a field you can see in a details page, +such as `cards` or `accessGroups`. For example: + +.... +GET /api/cardholders?sort=id&top=10000&fields=firstName,lastName,cards +.... + +=== Hrefs are URLs as well as identifiers + +The fields called `href` in the cardholder summary are URLs, +and hopefully you have followed one already. Some will return you a page +of data, and some will 404. We call them _hrefs_ rather than URLs +because they are HTML references that, in our case, happen to be HTTPS +URLs. + +Hrefs are very important. Each object in Command Centre--events, +alarms, items, connections between them--has one that identifies it. +You will be sending many of them in the bodies of your requests. + +=== Cardholder detail + +Follow one of the href links on the summary page: + +.... +GET /api/cardholders/1234 +.... + +1234 will be a different number on your system. The API documentation +uses the syntax `/api/cardholders/{id}`. Ignore the braces! There are no +braces in our URLs. + +That GET shows you everything the REST API can tell you about the +cardholder (footnote: not quite everything. Mobile credentials and PIV +and PIV-I cards have blobs of data that do not come out unless you ask +for them, because they are so large). The developer documentation helps +interpret it. + +[TIP] +==== +The differences between a summary page and a detail page are: + +* A summary page is a search. You pass it search filters, an item limit, and sorting and pagination + instructions and it will return any number of items. A detail page will only tell you about one. + +* A detail page will return more fields than a summary page, by default. However you should tell + both to only return the fields you need. + +==== + +The API documentation makes heavy use of the terms _summary_ and +_detail_. You see the summary of an item at root URLs such as +`/api/cardholders` and `/api/access_groups`, returned in an array of +many items of the same type as the results of a search. You see the +detail of a lone item by following the item’s href. + +It worth becoming familiar with the structure of a cardholder in JSON +because the REST API uses it for summary and detail pages, and it +expects very nearly the same structure when you create or modify a +cardholder. They vary in the quantity of fields and their levels in the +document. + +== First GETs part two: events + +=== List all events + +This returns 1000, starting with the first recorded: + +.... +GET /api/events +.... + +If it takes a while, it is because the JSON viewer extension in Chrome +is pretty-printing it. + +From there you can following the `next` link to get another thousand. +When you have extracted all the events out of Command Centre, an +`updates` link will replace `next`. The `updates` URL is a long poll +link: GETting it will block until more events arrive, or the call times +out. + +If you are writing a program that will extract all events out of Command +Centre you should set `top` (described in the API documentation) as high +as you can. Command Centre will cap it at 10,000. You do not gain much +performance after a couple of thousand, but taking it higher reduces the +number of requests. + +.... +GET /api/events?top=5000 +.... + +It might take a while for the JSON viewer to render all that JSON. + +=== List all alarms + +The alarms interface only returns alarms that have not been processed, +i.e., those that are 'current'. After an operator processes an alarm, it +is merely an event with extra fields. + +.... +GET /api/alarms +.... + +That will return at most 100 alarms. You can follow the `next` link to +get more, until you have got them all and an `updates` link replaces it. +The `updates` URL is a long poll: GETting it will block until more +alarms occur or the call times out. + +== Back into the theory + +This section is a grab-bag of facts you need before going further. + +=== API controllers + +_Controllers_ are different parts of the REST API. Not to be confused +with the controller hardware Gallagher also produces, API controllers +have the same name as the part of the request URL after the leading +`/api`. The main ones are `alarms`, `events`, and `cardholders`. `items` +is there to support searching for events. `card_types`, `competencies`, +`access_groups`, `roles`, `operator_groups`, and `locker_banks` let you +find items to attach to cardholders. + +All controllers’ names are plural, and pothole_cased. You can find links +to them all with + +.... +GET /api +.... + +=== Why we need an operator + +Everything that happens to a cardholder happens because an operator did +it. The operator could be a person working in one of the thick clients, +or it could be one of the other APIs, but whenever a cardholder changes, +Command Centre must have an operator to pin it on. + +Having an operator allows Command Centre to enforce privileges. You +limit what your REST operator can do in case the client has bugs (and +starts DELETEing URLs instead of GETting them) or the API key becomes +known to the other side. + +It also helps auditing. Each cardholder change causes an operator event, +with the operator and cardholders as related items. Interactive changes +use the workstation as the source, and REST changes use the REST Client. +You can run reports that filter on the source and operator to monitor +your integration. + +=== The request process + +This is shown in a link:auth_flow.dot.pdf[rough flowchart]. + +All HTTPS requests start like this: + +[arabic] +. The client and server establish an encrypted channel. Part of that is +a certificate exchange. The channel makes the following conversation +safe from eavesdroppers but does not confirm the identity of either +side. +. Unless you have configured your client not to, it verifies the +identity of the server (authenticates it) by examining the contents of +the certificate that came from the server during the previous step. If +the client does not like the certificate that came from the server, it +drops the connection. Command Centre will complain to its log file when +this happens but because it did not receive a request, will not create +an event. +. If the client trusts the server it sends its request along with a +secret that proves it is who it says it is. In our case that is an HTTP +header containing the API key. + +So far that has been a normal HTTPS conversation, the same as what +happens with every web site you visit in a browser. From here on is +specific to Command Centre. + +[arabic, start=4] +. The server looks for the API key in the `Authorization` header and +finds the matching REST Client (footnote: capitalised to mean the +configuration item in Command Centre, not the REST client software on +the other end of the TCP connection) in the database. If it cannot find +one, it will raise an alarm "A REST connection was attempted with an +invalid API key". +. If you did not disable pinned client certificates in the server +properties (Web Services tab), or if you are running 8.50 and the REST +Client item has a thumbprint on it (in the API Key tab), it checks the +thumbprint of the request’s certificate against the one on the REST +Client item. If they do not match, it responds with a 401 and raises an +alarm "A REST connection was attempted with an invalid client +certificate". The server does not check the client certificate’s chain +of trust. <<_client_side_certificates>> has all the +details of why you would want your server to check client certificates +and how to create them. +. It checks the source host’s IP number against the REST Client item’s +IP filters. If it does not match, it responds with a 401 and raises an +alarm "A REST connection was refused because of the connecting IP +address does not match the IP filter on the REST Client __name of your +REST Client__". +. It checks that it has a license for the controller that will handle +the request. If it does not, it sends a 403 response containing the +string "Feature not licensed". +. It creates a new session for the operator, if there isn’t one ready, +then compares what the request is asking for against the REST Client’s +operator’s privileges from the session. If the privileges do not allow +the operation that the client requested, the server will respond with a +400-level error and a message in the body. + +If all those steps succeed, the API controller processes the request, +logs an operator event if something changed, and returns a result. + +The alarms above have a default priority of medium-high. The server +raises them for two reasons: while developing, it is useful to have a +little more diagnosis coming out of the server, and in production, it is +good to know when your API is being probed. + +If too many bad requests arrive too quickly, the server will assume it +is under attack and will log an alarm at maximum priority, then will +remain silent on the matter until the attack stops. + +Errors also go to +`%PROGRAMDATA%\Gallagher\Command Centre\Command_centre.log`. + +== First POST and search + +=== Create a cardholder + +In Postman: + +image::postman_create_cardholder_1.png[POST to create a cardholder,title="POST to create a cardholder"] + +Notice that there are two headers set: `Authorization` contains the API +key and `content-type` tells the server that the body is JSON. + +This document uses this shorthand to represent that kind of HTTP query: + +---- +// POST /api/cardholders +{ + "firstName": "New", + "lastName": "Cardholder", + "division": { + "href":"https://localhost:8904/api/divisions/2" + } +} +---- + +The first line gives the verb and the file part of URL. It needs the protocol, host, and port added, +to form the final request: a POST to `https://yourserver:8904/api/cardholders`. The rest is the +body. + +When you create a cardholder you must specify the division and either +the first or last name, so this example is about the shortest you can +get away with. + +Look at the response from the POST. It contains a `Location` header +giving the URL of our new cardholder. + +image::postman_create_cardholder_result.png[Create cardholder result,title="The result of creating a cardholder"] + +You could GET that URL to see what you created, or… + +=== Search for a cardholder + +.... +GET /api/cardholders?name=new +.... + +That will return all the cardholders with 'new' in their name. It is +case-insensitive. + +To be more precise: + +.... +GET /api/cardholders?name="cardholder, new" +.... + +Quotes make it a full string match, rather than a substring match. It is +still case-insensitive. + +Note how Command Centre matches your search string against a +concatenation of the cardholder’s last name, a comma, a space, and the +first name. It only does that if the cardholder has both names set. +Otherwise it just uses the one. + +Also note that Chrome will turn the space into `%20`. + +You should see your new cardholder in the results of both those queries. + +== Cardholder flat fields + +"Flat fields" isn’t a term the REST API uses but it means the simple data like names, description, +and PDF values that sit at the top level of a cardholder and do not have structures of their +own. Other data such as cards, access group memberships, and competency assignments are one level +down, in arrays, and contain other fields. + +=== Setup: give a cardholder access groups and PDFs + +For a cardholder to have a PDF, both need to be on the same access +group. You cannot create PDFs or assign them to access groups via REST +so you must do that in the Configuration Client. Adding cardholders to +groups is possible via REST of course, but that is easier if you have an +existing group membership to compare your efforts against, so for now we +will do that in the client as well. + +==== Create some PDFs + +In the Configuration Client, Configure -> Personal Data Fields (second +from the top). + +Add -> New Personal Data Field. Call it 'email' and set the type (on the +Type tab) to Email. + +You might as well make a few more with different data types. Make at +least one text, because they have no constraints and are easiest to +experiment with. + +image::pdf_create_1.png[Create a PDF in Configuration Client,title="Create a PDF in Configuration Client"] + +==== Create at least two access groups, add the PDFs, and add your cardholder + +In the configuration client, Manage -> Access Groups, right-click menu, +New -> Access Group. + +Open the cardholder and PDF lists out of the Manage menu so that you can +drag items out of them. + +Drag your new cardholder to the Cardholder Membership tab of the access +group. + +Drag your PDFs to the Personal Data tab of the access group. + +Repeat! + +image::pdf_to_club.png[Add a PDF to a group in Config Client,title="Add a PDF to a group in Config Client"] + +Save everything in the configuration client then reload your cardholder’s details in your REST +client to see what PDF values and group memberships look like in JSON. These sections in the +cardholder API documentation cover it: + +* 'Cardholder detail' gives the layout of a cardholder’s detail page. +* 'Cardholder PDF' describes the items in the `personalDataDefinitions` array. +* 'Cardholder access group' describes the items in the `accessGroups` array. + +Now you can change some of those values. + +=== Change a name, authorise, change simple PDFs, set user code, etc. + +This example changes a cardholder’s first name and two PDFs, authorises +it (de-authorised cardholders always fail access checks), turns on a +flag that allows extra unlock time on doors, and sets the user code +(which is a number you can use at keypads): + +---- +// PATCH /api/cardholders/{id} +{ + "firstname": "Jeremiah", + "@datePDF": "2099-03-31", + "@email": "a@b.com", + "authorised": true, + "useExtendedAccessTime": true, + "userCode": "1234" +} +---- + +It looks like this in Postman: + +image::postman_patch_cardholder_1.png[PATCH a cardholder in Postman,title="PATCH a cardholder in Postman"] + +It looks a lot like that in the cardholder’s details page too, so here +is the rule: + +TIP: When PATCHing flat fields on a cardholder, send back the same kind of JSON you got from a GET. + +=== Image PDFs and Base64 + +Here is a cutting from the details page of a cardholder with an image +PDF: + +---- +// GET /api/cardholders/{id} +{ + ... + "@datePDF": "2099-03-31T00:00:00Z", + "@Email": "a@b.com", + "@Mugshot": { + "href": "https://localhost:8904/api/cardholders/325/personal_data/8449" + } + ... +} +---- + +Notice that the image PDF does not show in a cardholder’s details, +because they can be massive. Instead you get a URL. If you follow that +link you will see the image. + +In order to send binary data in JSON (which cannot contain non-printable +characters), you have to encode it to Base64. This turns raw bytes into +a string of letters, numbers, plusses, and slashes (64 possible +characters), sometimes with equals signs on the end. It also increases +the size of the data by about a third. You can put the string between +quotes and send it like any other PDF: + +---- +// PATCH /api/cardholders/{id} +{ + "@photo": "Kilobytes+of+Base64+encoded+data===" +} +---- + +TIP: If you see a load of what looks like garbage ending with equals signs, it is probably Base64. + + +== Cards + +Meaning credentials. In this section you will see how to give a +cardholder a card and modify existing cards. + +=== Adding, updating, and deleting cards + +Like all cardholder modifications, you do this with a PATCH to the +cardholder href. However a card is not a flat field: it is a member of +an array in the cardholder object called `cards`. To add an item to the +cards array, or change one, you pass in an object also called `cards`. +True to previous advice, we do all operations in one PATCH. + +Borrowing from the API documentation: + +____ +The cards object can contain three arrays, named `add`, `update`, and +`remove`. Every element you put in those arrays should be in the card +schema that you see in a cardholder detail. + +Each element of the `add` array will need a `type` member, at the very least. The only card field +that does not make sense here is `href`, because an href in a card block names an existing card to +change, but here you are creating one. + +The example below adds two cards: one has nothing more than the type, so it will receive a computed +number and issue level, and blank `from` and `until` dates. The other is a mobile credential with a +custom initial state 'Pending sign-off'. You can tell it is a mobile credential because only they +have `invitation` blocks. + +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 (making it valid forever). + +The only field that makes sense in an element of the remove array is `href`. + +Do not put the same href in both the `update` and `remove` arrays. +____ + +Here is the example. As well as adding two credentials, modifying a third, and removing another, it +authorises the cardholder and sets a PDF called `employeeID` just to remind you that you can combine +operations: + +---- +//PATCH /api/cardholders/{id} +{ + "authorised": true, + "@employeeID": "THX1139", + "cards": { + "add": [ + { + "type": { + "href": "https://localhost:8904/api/card_types/354" + } + }, + { + "type": { + "href": "https://localhost:8904/api/card_types/600" + }, + "number": "Jock's iPhone 8", + "status": { + "value": "Pending sign-off" + }, + "invitation": { + "email": "jock@example.com" + } + } + ], + "update": [ + { + "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d", + "issueLevel": 2, + "until": "" + } + ], + "remove": [ + { + "href": "https://localhost:8904/api/cardholders/325/cards/77e8affe7c7e4b56" + } + ] + } +} +---- + +Notice how the hrefs of a card include the cardholder’s href and end +with a long identifier. That is because a card is a property of a +cardholder. Do not read anything more into it: treat it as opaque. + +=== Don’t delete cards: disable them + +That wasn't a great example because generally, when you have reason to stop a card from working, you +want: + +* a permanent reminder of why you did it, +* to prevent another operator assigning the same card number to them later (so that if someone finds + a card on the ground and tries it, it won’t open the building), and +* to know who a lost card was assigned to in case it turns up again. + +Command Centre achieves the first two of these goals if you delete an old card but it is easier if +you leave it card in the system, non-functional. You can set its end date into the past or set its +state to one of the disabled states. + +WARNING: Deleting cards loses information about it, and can reduce the security of your building. + +Disable them instead. + + +== Group memberships + +In this section you will add your cardholder to an access group and +modify the membership. + +=== Add an access group membership + +You will need the href of your cardholder that you used in the +link:#_cardholder_detail[cardholder detail GET] or the +link:#_adding_updating_and_deleting_cards[cardholder PATCH], or that came +back from your POST when you link:#_create_a_cardholder[created a +cardholder]. + +You also need the href of your access group. You can see all your access +groups by querying the access groups controller. Hint: `GET /api` then look +in the block called `accessGroups`. Extra hint: `GET /api/access_groups`. + +When you have those two hrefs, substitute them into: + +---- +// PATCH /api/cardholders/325 <1> +{ + "accessGroups": { + "add": [ + { + "accessGroup": {"href": "https://localhost:8904/api/access_groups/5388"} // <2> + , "from": "2017-01-31T02:11:00Z" + , "until": "2037-01-31T02:11:00Z" + } + ] + } +} +---- + +<1> is the href of your cardholder. + +<2> is the href of your access group. + + +(Remember that the first line does not go into the body of your HTTP query, and your actual URL will +start with `https://` with a host and port. Also note the alternative comma style: putting them on +the start of the line makes commenting them out easier.) + +If you use the wrong access group identifier, or your operator does not +have 'Modify Access Control' on the access group, you will be told: + +---- +{ + "message": "Invalid access group href: https://localhost:8904/api/access_groups/53888" +} +---- + +When you get it right, the server will return 204 and next time you GET +your cardholder the result will contain: + +---- +// GET /api/cardholders/325 +{ + ... + "accessGroups": [ + { + "href": "https://localhost:8904/api/cardholders/325/access_groups/1069", // <1> + "accessGroup": { + "name": "Boney M", + "href": "https://localhost:8904/api/access_groups/5388" + }, + "status": { + "value": "Active", + "type": "active" + }, + "from": "2017-01-31T02:11:00Z", + "until": "2037-01-31T02:11:00Z" + } + ] +} +---- + +The 'Cardholder access group' section of the cardholder API +documentation helps with interpreting that. + +The marked URL is the href of the cardholder's group memberrship, which is a link between the +cardholder (ID 325, in my case) and the access group (ID 5338). It starts with the href of the +cardholder, because it is a property of that cardholder and serviced by the cardholders controller, +but do not try to interpret it more. Certainly do not read anything into the number on the end +(1069), and do not be surprised if you have an item with the same ID. + +<<_the_importance_of_api>> and <<_identifiers_in_your_app>> go into what you should not do with +hrefs. + +=== Edit an existing group membership + +Correct the URL of the cardholder and the access group membership in this PATCH: + +---- +//PATCH /api/cardholders/325 +{ + "accessGroups": { + "update": [ + { + "href": "https://localhost:8904/api/cardholders/325/access_groups/1069", + "from": "2027-03-09" + } + ] + } +} +---- + +If it returns a 204, GET your cardholder again and look at its access +group memberships. The from date should have changed from 2017 to 2027, +and the membership href will be different. + +The server changes the href after an update to prevent race conditions +when there are two operators active. It means the two of you cannot +change the group membership at the same time - the second one in will +fail. The advice, therefore, is to update your cardholder as soon as +possible after retrieving its details (footnote: probably good advice +for a fetch and update on any API). + +CAUTION: *Do not cache the hrefs of links between items*. They change with operator actions. + + +== Create a cardholder, cont. + +Now that you have access groups, cards, and PDFs, you can create a fully +configured cardholder in one request. Here is an example that creates a +cardholder, sets a PDF called 'email', puts it in an access group (which +is necessary for the PDF to work), and gives them a card. + +---- +// POST /api/cardholders +{ + "firstName": "New", "lastName": "Cardholder", + "description": "Test cardholder", + "division": {"href":"https://localhost:8904/api/divisions/2"} + "useExtendedAccessTime": true, + "usercode": "1234", + "@email": "a@b.com", + "accessGroups": [ + { + "accessGroup": {"href": "https://localhost:8904/api/access_groups/334"}, // <1> + "from": "2019-01-01" + } + ], + "cards": [ + { + "type": {"href": "https://localhost:8904/api/card_types/342"}, // <1> + "number":"3162" + } + ], + "zzzcompetencies": [ // <2> + { + "competency": {"href": "https://localhost:8904/api/competencies/5394"}, + "enabled": true, + } + ] +} +---- + +<1> These identify an access group and card type for your new cardholder. +<2> Never mind the competency yet. The `zzz` makes the server ignore it. + +You will need to change the marked numbers to the IDs of an access group and a card type on +your system. You can get those with: + +.... +GET /api/access_groups +.... + +and + +.... +GET /api/card_types +.... + +In Chrome, those calls will look like +`https://_yourhost_:8904/api/access_groups` and +`https://_yourhost_:8904/api/card_types`. + +An actual application would also find the href of the correct division, +but for today is it safe to assume that the href of the root division is +`.../divisions/2`. + +After changing the `334` and the `342` put the JSON into Postman and POST it to +`/api/cardholders`. It should return you the href of a new cardholder, as it did in +<<_create_a_cardholder>>. + +The `zzz` is in there to stop the REST API trying to add a competency to +your new cardholder, which would fail because you have not created a +competency yet. There is nothing special about three 'Z’s -- the server +just ignores anything it does not recognise. + +IMPORTANT: *The server will ignore fields it does not recognise*. Beware of this, as you may think +your calls are succeeding when in fact they ard doing less than you want them to. + + +That is more of an advantage that a disadvantage. It means we can write +clients that degrade gracefully on Command Centre servers that are not +the most recent version or are missing licences. Also, introducing typos +to the names of your JSON objects is a convenient way of commenting them +out. You can also prepend lines with `//`. It is not valid JSON but you +can get away with it for now. + +Back to our example. If you want to create a cardholder with a +competency: + +. make a competency in the Configuration Client, +. find its href from the competencies controller (`GET /api/competencies`), +. change your JSON (remove the `zzz` and change the 5394), and +. try the POST again. + +It should fail, complaining that you cannot have two cards with the same card number. Change the +`3162` and try again (or change `number` to `znumber` and let Command Centre pick a card number for +you -- probably 3163). + + + +== Coding considerations + +If you don't mind a return to theory, here are several things you should keep in mind when building +an integration against this API. + +=== Recap + +* `/api` returns links to summary pages. Why that is important is in + <<_the_importance_of_api>>. + +* At time of writing, the URLs of most summary pages end with the name of the +link:#_api_controllers[controller], such as `cardholders` or `access_groups`. Others end with their +specific purpose, such as `card_types/assign`, which returns the card types your operator can assign +to people. + +* Summary pages show you many items without much detail of each. You can add sorting and pagination +parameters. link:#_cardholder_summary[Cardholders], link:#_first_gets_part_two_events[alarms and +events], and link:#_other_command_centre_items[other items]. Tell the API to sort its results by ID +because it is quicker, and more reliable when operators are changing the database. Unless you’re +writing a user app and really must have your results sorted by name. + +* You can also add filters to summary pages, turning them into search pages. See +<<_search_for_a_cardholder>> and <<_event_filters>>. +* In v8.00+ you can add fields from the details page to the summary page +of items, and in 8.40+, events. Or you can specify the exact fields you +need, if you want to save traffic. +* You walk the result set using links named `next` and `previous`. + +* Detail pages give you more on an item, but only one item at a time. Their URLs end with short +alphanumeric identifiers. link:#_cardholder_detail[A cardholder], for example. There is not much +use for detail pages in the API after v8.00, since you can add all their fields to the summary pages. + + +* To create a cardholder, link:#_first_post_and_search[POST the +cardholders controller]. The body of the POST is pretty much the same as +you get from a GET of an existing cardholder, but with fewer fields. +* To update a cardholder, link:#_cardholder_flat_fields[PATCH its href]. +That includes link:#_cards[adding cards]. +* If you are changing PDFs or flat fields, the body of the PATCH looks a +lot like what you got from a GET to the same URL. Put `@`-symbols on the +front of your PDF names. +* If you are updating cards, lockers, access groups, relationships, +competencies, or operator groups, you will be sending arrays called +`add`, `remove`, and `update` inside objects called `cards`, `lockers`, +etc. + +=== Only one cardholder at a time + +In all these flat field, card, and group membership examples you were +working on one cardholder at a time. That is the only way you can +operate, because the cardholder you are changing is named by the URL. If +you want to change many cardholders, you must do it in a loop. + +On the upside, you can change everything about the cardholder in one +PATCH. In fact, it is most efficient to do so. While you can use DELETE +to remove one card or group membership or relationship at a time, you +will see much better throughput if you combine it with the other changes +for that cardholder and send them as one PATCH. The same applies to +creating a cardholder: it is much quicker to do it as one POST than as a +POST followed by one or more PATCHes. The other advantage is that any +one is atomic: all the changes you put in the body happen, or none of +them do. So: + +TIP: When creating a new cardholder, do it all in one POST. + +When modifying an existing cardholder, do it all in one PATCH. + +=== The importance of /api + +Forget all the URLs you have seen so far, except the first, and do not +write them into your applications. The only address that your +application should have coded into it is `/api`. You can learn every +other address you need with a GET of that. It will return a table of +contents like this: + +---- +// GET /api +{ + "version": "7.90.0.0", + "features": { + "accessGroups": { + "accessGroups": { + "href": "https://localhost:8904/api/access_groups" + } + }, + "accessZones": { + "accessZones": { + "href": "https://localhost:8904/api/access_zones" + } + }, + "alarms": { + "alarms": { + "href": "https://localhost:8904/api/alarms" + }, + "updates": { + "href": "https://localhost:8904/api/alarms/updates" + }, + "divisions": { + "href": "https://localhost:8904/api/divisions/view_alarms" + } + }, + "alarmZones": { + "alarmZones": { + "href": "https://localhost:8904/api/alarm_zones" + } + }, + "cardholders": { + "cardholders": { + "href": "https://localhost:8904/api/cardholders" + }, + "updateLocationAccessZones": { + "href": "https://localhost:8904/api/access_zones/update_cardholder_location" + }, + "changes": { + "href": "https://localhost:8904/api/cardholders/changes" + } + }, + "cardTypes": { + "cardTypes": { + "href": "https://localhost:8904/api/card_types" + }, + "assign": { + "href": "https://localhost:8904/api/card_types/assign" + } + }, + "competencies": { + "competencies": { + "href": "https://localhost:8904/api/competencies" + } + }, + "doors": { + "doors": { + "href": "https://localhost:8904/api/doors" + } + }, + "events": { + "events": { + "href": "https://localhost:8904/api/events" + }, + "updates": { + "href": "https://localhost:8904/api/events/updates" + }, + "eventGroups": { + "href": "https://localhost:8904/api/events/groups" + }, + "divisions": { + "href": "https://localhost:8904/api/divisions/view_events" + } + }, + "fenceZones": { + "fenceZones": { + "href": "https://localhost:8904/api/fence_zones" + } + }, + "inputs": { + "inputs": { + "href": "https://localhost:8904/api/inputs" + } + }, + "items": { + "items": { + "href": "https://localhost:8904/api/items" + }, + "itemTypes": { + "href": "https://localhost:8904/api/items/types" + }, + "updates": { + "href": "https://localhost:8904/api/items/updates" + } + }, + "lockerBanks": { + "lockerBanks": { + "href": "https://localhost:8904/api/locker_banks" + } + }, + "macros": { + "macros": { + "href": "https://localhost:8904/api/macros" + } + }, + "outputs": { + "outputs": { + "href": "https://localhost:8904/api/outputs" + } + }, + "personalDataFields": { + "personalDataFields": { + "href": "https://localhost:8904/api/personal_data_fields" + } + }, + "roles": { + "roles": { + "href": "https://localhost:8904/api/roles" + } + } + } +} +---- + +You should parse the `features` block of that page for the URLs of the +calls you need. It contains one block for each controller in the API: +cardholders, events, alarms, etc. Some of those blocks contain one more +block, in turn containing an href for the base call for that controller. +Other controllers (`card_types`) contain more than one, each containing an +href for a different call. For example, the events and alarms +controllers also provide a `divisions` call which lists the divisions in +which your operator has the privilege to see events and alarms, +respectively. + +==== Do not code URLs into applications + +Because Gallagher reserves the right to change them. If you start at +`/api`, your application will stay compatible through Command Centre +upgrades. + +While it is tempting to hard-code a string `/api/cardholders` into +your application, it is not that much more development effort to get +that URL from the contents page and make your code forward-compatible. +Help yourself to the sample C#, starting with ClientManagerAsync.cs. + +=== Identifiers in your app + +There are two API identifiers: IDs and hrefs. + +The short alphanumeric string that comes in a field called `id` is there purely for use in query +parameters, such as filters. Because you add them to URLs they have to be short and free of +punctuation, so we do not use the longer identifier that comes next. <> shows how to +use them to find events. + +CAUTION: *Do not treat IDs as integers*. They are alphanumeric. A future version may add letters. + +The URL that comes in a field called `href` is how you reference +objects, both as addresses in your own GETs, DELETEs, and PATCHes, but +also in the bodies of those requests and POSTs when you need to connect +two objects. When adding a card to a cardholder, for example, you need +to send the href for the new card’s card type. You would find that href +using the card_types controller. As another example, when connecting two +cardholders with a relationship, you need to PATCH the href of one of +the cardholders with the href of the role (from the roles controller) +and of the other cardholder (from a search of cardholders). + +==== Some hrefs are meant to 404 + +Many hrefs will respond to a GET, but some exist purely for +identification: cards, relationships, and group memberships, for +example. Those hrefs are for use inside the body of a PATCH to modify a +cardholder. You can DELETE some of them but GETting one of these will +always return 404. + +==== Many hrefs are dynamic + +Some hrefs change regularly: access group membership IDs, for example, +change every time you modify the underlying membership, even if you only +change its end-date. For that reason you must start all changes to a +cardholder with a GET of that cardholder. That will give you up-to-date +hrefs for linked group memberships, roles, cards, etc., which you can +then use in the body of a PATCH. + +==== You can use them to cross-reference cardholders in an integration + +// how do you italicise text after an underscore? + +External systems will have their own user identifiers: staff or student +ID numbers, usernames, or national IDs. You might like to store that ID +in a PDF and use the PDF search (`/api/cardholders?pdf_yyy=__zzz__`, +described in the developer documentation) to find the href of your +cardholder when it comes time to synchronise. Then you do not have to +store a copy of the href. + +That could be slow for large numbers of cardholders. Instead, you could +retain the href returned to you when you created your cardholder and use +that forever after. There would be no need for a PDF holding the +external identifier inside Command Centre. + +However that is no use for cardholders that your integration did not +create. Plus you risk losing your cardholder if its href ever changes +(which could occur if another operator or integration deletes and +recreates it, or Gallagher changes the layout of hrefs). + +The recommended option is a blend of the two: give every cardholder a +PDF containing their external ID and cache their href externally. If +your cache does not have it, or if using it returns a 400-level error, +refresh your cache using a PDF search. + +==== Subtract the host and port then replace them with values from your integration’s configuration + +You will note that every cardholder href begins with the scheme, host, +and port: `https://localhost:8904` in these examples. I am going to +contradict earlier advice ever so slightly and suggest that you drop the +// BUG does not italicise the port +`https://_host_:port` from the front of an href before storing it, then +add the host and port from your integration’s configuration before using +it again. By doing that you give yourself the flexibility to change the +hostname or port without invalidating your cache of hrefs. + +For example, for a cardholder with href +`https://localhost:8904/api/cardholders/123`, store +`api/cardholders/123` in your database. The application must have +`locahost` and `8904` in its configuration somewhere (how else could it +make HTTP requests?), so when it comes to find that cardholder again, +prepend `https://localhost:8904/` to the stored value. When your IT +people change the server’s hostname or shift the service to another +port, all you have to do is change your configuration. Which you had to +anyway. + +=== Do not build your own hrefs from IDs + +Buoyed with confidence gained following links around our API, you will +be tempted to store just the parts of hrefs that seem to matter and +reconstruct them later. In the interests of forward compatibility: + +CAUTION: *Do not interpret href paths, and do not build your own.* We change them. + +As a reminder, the parts of a URL relevant to us are the protocol +('scheme'), host, port, path, and query: + +.... +scheme://host:port/path?query +.... + +The scheme will always be `HTTPS`: you can take that as read. By all means, +replace the hostname and port number and add your own search parameters +to the query on the end of URLs you take from GET /api, but please do +not tinker with the path. Treat that as opaque. + +For example, in version 8.10 the path to a locker was + +.... +/api/locker_banks/locker_bank_id/lockers/locker_id +.... + +In 8.20 it changed to + +.... +/api/lockers/locker_id +.... + +Client code that inserts a locker bank ID and locker ID into the +hard-coded string `/api/locker_banks/{1}/lockers/{2}` will work against +an 8.10 server but fail when the server upgrades to 8.20. Client code +that takes the href from a locker bank page will work with both +versions. + +=== Sort by ID, and get all summary pages without delay + +By default, item summaries arrive sorted by name. That can cause a race +condition: + +[arabic] +. You get the first 1000 cardholders, sorted by name. +. Another operator (or your own update) changes the name of one of those +cardholders to part of the alphabet you have not collected yet, or vice +versa. +. The next 1000 cardholders will either contain one you already +received, or will skip one you did not. + +There are three things you can do to reduce this risk: + +* Sort by ID. Command Centre does not recycle IDs so no cardholder can +slip into part of the database you have already extracted. +* Collect hrefs from the summary pages, following the next link until it +no longer arrives, before using any of them. This means you collect +everyone in the minimum possible time, and if you do update your +cardholders you will not mess in your own yard. +* Get thousands at a time so that you make fewer calls. + +=== Monitoring for cardholder changes is much better in 8.30 + +Version 7.90 supports change tracking through the events API. If you +filter for operator events you will be informed of every change using +long polls (below): the href of the affected cardholder or access group +will be in the event. In 8.00+ the href of the operator who made the +change will also be in the event. + +Version 8.30 adds a call to the cardholders controller that makes +synchronising them much simpler. It can tell you which fields changed on +a cardholder and what their values were before and after the change, and +what their current values are. All the details are in a section called +"Cardholder changes" in the developer documentation but here is a +quick run-down: + +[arabic] +. Send a GET to request a bookmark to the current head of the list of +cardholder changes. +. Synchronise your system with Command Centre using other cardholder +methods. It does not matter how long this takes. +. GET the bookmark you received previously. That will send you all the +changes that happened since then, plus a new bookmark. +. Process those changes, if there were any. Sleep if there were not. +. Go back to step 3. + +To reduce the work you have to do and chatter on the wire using `filter` +and `fields` query parameters. `filter` limits the changes you receive +to those that you’re particularly interested in (you might not care +about anything except changes to PDFs, for example), and `fields` lets +you request more or less data about each change and its cardholder. + +=== Long polls + +A _long poll_ is a way for HTTP servers to send updates to interested +clients. The client registers its interest by sending an HTTP GET, and +the server pushes to the client by responding when something of interest +occurs. + +Using telephone calls as an analogy, a traditional poll would have the +client calling the server, the server answering, then hanging up +immediately if there was nothing to report. The client would then need +to wait a time and try again. + +If the telephone server supported long polls, however, it would leave +the incoming call ringing until it had something to say. The client +would carry on about its business until the server picked up. After +hearing the server’s response the client would call back when it wished. +Immediately if it was in a particular hurry. + +=== Benchmarks + +These are the results of informal performance tests of Command Centre +7.90 running on reasonably capable hardware. + +You will not achieve these numbers without following the advice in the +'efficiency tips' sections of the developer documentation. + +==== Extracting events + +Sustained an average of two to four thousand per second from a database +of four million. + +==== Extracting 12,000 cardholders + +Extracting their cards, access groups, and PDFs took three to four +minutes on a v7.90 server. The process was to request a summary page of +10,000 cardholders, then the remaining 2,000, then iterate through all +their hrefs, getting their details pages one by one. + +That is a poor approach these days. Extracting the same fields for the +same *12,000 cardholders took 12 seconds* on the same server running +v8.00. This process used the `fields` parameter to add cards, access +groups, and PDFs to the summary page so that the test did not have to +get any detail pages. + +==== Extracting 200,000 cardholders + +Now on 8.30 and different hardware, extracting the names of 200,000 +cardholders took one minute with `top=1000&sort=id`, or 28 minutes +without. That is how important those query parameters are. Use them! + +Part of the three-times speedup from the previous test will be due to the absence +of PDFs: they can be expensive to extract. + +==== Creating cardholders + +Ten thousand took an hour. The test added a cardholder with a card and a +handful of group memberships and PDFs. + + + +== Advanced events + +=== Event filters + +The developer documentation is authoritative on how to restrict your +event results, but here is an introduction. + +You can filter by the occurrence date/time, the source item, the event’s division (which is almost +always the source's division), the event’s type, the type’s group (all event types are grouped, and +picking a group is synonymous with picking a few types), or the event’s cardholder. + +In 8.70 and later you can limit the events to those related to a particular item. For example, by +specifying a cardholder's ID you will receive the same events that would appear in Command Centre's +activity report for that cardholder. + +// BUG doesn't want to use fixed-width for the right column +[cols=",",options="header",] +|=== +|To filter by … |Add a query parameter called… +|Event type |`type` +|Event type group |`group` +|Cardholder |`cardholder` +|Source |`source` +|Division |`division` +|Date |`after` and / or `before` +|Related item | `relatedItem`, and be running 8.70 or later. +|=== + +For example, to find all card events ('access granted', 'access denied', +etc.): + +.... +GET https://localhost:8904/api/events?group=23 +.... + +The API documentation shows you where the 23 comes from. + +To watch two cardholders: + +.... +GET https://localhost:8904/api/events?id=325,8445 +.... + +=== IDs to use in filters + +To keep the query strings manageable, these filters take short, +alphanumeric strings as IDs rather than the URLs that the API generally +uses for identifying items. At the version of writing (8.70) these IDs are low numbers, +but we reserve the right to introduce letters in the future, so do not +interpret them as integers. + +These are your options for finding the IDs you need to build a filter +string: + +* look at one of the events you want in the API. Everything you can filter by is there; +* look at `/api/events/groups` for event types and their +groups; +* look at `/api/cardholders` for cardholders; +* look at `/api/items` for all other items, using a type filter of its own from looking at +`/api/items/types`; +* if running v8.00 or later, look at the controllers for doors, outputs, alarm zones, access zones, +and fence zones, linked from `/api`; +* If running v8.10 or later, look at the inputs controller. + +For example, to find all your doors you would + +.... +GET https://localhost:8904/api/doors +.... + +(after getting that URL from `GET /api`, of course) + +The doors controller arrived in 8.00, so if you have 7.90 or older you would + +.... +GET https://localhost:8904/api/items?type=11 +.... + +That 11 came from + +.... +GET https://localhost:8904/api/items/types +.... + + +=== Filtering by date + +Even though the `before` and `after` fields are only accurate to a second, +filtering by date is 'smart' for reports: the result set will not +include events that occurred during the `before` second. For example, +`before=2019-01-01T00:00:00Z` will not return you any events from 2019 or later years. +Pass the `before` parameter for one report as the `after` parameter of +the next. You never need to use `23:59:59`, and there is no risk of +missing an event that happens in the last second, or in a leap second. + +All date-times should be in ISO-8601. If you omit fields (such as +minutes or seconds) Command Centre will assume sensible defaults, but +the best advice is to be explicit (especially about the time zone). + +IMPORTANT: *Put a timezone specifier in all date-times!* Otherwise the outcome will depend on settings +on the server. + +=== Adding PDF values to the cardholders in events + +Card events such as 'access granted' use the door as the source but also +have a related cardholder. The event JSON includes the cardholder’s name +and href, but if you want to use your own identifiers for cardholders +you can also ask for a PDF to come out with the event. Do that by adding +`fields=defaults,cardholder.pdf_XXXX` where `_XXXX_` is the ID of the PDF. +Find that ID with a query to `/api/personal_data_fields`, adding +`?name="your_pdf_name"` if you want Command Centre to do the searching +for you. + +In order to see that PDF, your REST operator will need the appropriate +privileges. Otherwise the event will come out without the PDF. 'View +Cardholder' on the cardholder might not be enough: while PDFs are +visible by default, an operator can hide them, in which case your REST +client’s operator group will need to override that to readable or +read/write. + +=== Writing an interactive event viewer + +If I was writing an interactive application to monitor events as they +occurred, while also allowing browsing the event history, I would get +the most recent--enough to fill a screen--with: + +.... +GET /api/events?previous=true&top=20 +.... + +Then I would set an asynchronous task waiting on the `updates` link, +which would return with new events as they happened. + +At the same time I would follow the `next` and `previous` links to +collect more events as my user scrolled back and forth. + +=== Worked example: reading alarms + +What follows is a series of calls that collect alarms from the 7.80 +version of the alarms API. There may be extra fields in later versions +of Command Centre. + +==== Collecting all unprocessed alarms with one active forced door + +The initial HTTP GET of `http://localhost/api/alarms` returns all +unprocessed alarms: a bad login, a network problem, and two forced +doors, in this example. The second forced door is still open, so the +alarm is active and instead of links for processing it we have links for +force processing it, because you are not really meant to process active +alarms. + +Note they are in the order that they arrived at the server, not the +order they happened. Interesting pieces are in bold face. + +// enables bold +[subs="quotes"] +---- +{ + "alarms": [ + { + "href": "http://localhost:8904/api/alarms/289", + "id": "289", + "time": "2016-11-10T14:17:00", + "message": "*Operator logon failed* for FT Workstation on GNZ-PC1302", + "source": { "name": "FT Workstation on GNZ-PC1302" }, + "type": "Operator Logon Failed", + "priority": 3, + "state": "unacknowledged", + "active": false, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/289/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/289/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/289/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/289/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/289/process" }, + "process": { "href": "http://localhost:8904/api/alarms/289/process" } + }, + { + "href": "http://localhost:8904/api/alarms/296", + "id": "296", + "time": "2016-11-10T13:58:16", + "message": "*Fat controller - Command Centre comms interrupted*", + "source": { "name": "Fat controller" }, + "type": "Comms failed to Command Centre", + "priority": 6, + "state": "unacknowledged", + "active": false, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/296/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/296/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/296/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/296/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/296/process" }, + "process": { "href": "http://localhost:8904/api/alarms/296/process" } + }, + { + "href": "http://localhost:8904/api/alarms/301", + "id": "301", + "time": "2016-11-10T14:18:27", + "message": "*Warehouse door has been forced.*", + "source": { "name": "Warehouse door" }, + "type": "Forced Door", + "priority": 8, + "state": "unacknowledged", + "active": false, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/301/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/301/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/301/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/301/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/301/process" }, + "process": { "href": "http://localhost:8904/api/alarms/301/process" } + }, + { + "href": "http://localhost:8904/api/alarms/306", + *"id": "306"*, + "time": "2016-11-10T14:21:41", + *"message": "Front door has been forced."*, + "source": { "name": "Front door" }, + "type": "Forced Door", + "priority": 8, + "state": "unacknowledged", + *"active": true*, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/306/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/306/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/306/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/306/acknowledge" }, + *"forceProcess": { "href": "http://localhost:8904/api/alarms/306/process" }* + } + ], + "updates": { "href": "http://localhost:8904/api/alarms/updates?id=306" } +} +---- + +==== Collecting updated alarms after closing the door + +Next we close the front door, the kicking in of which caused alarm 306, +and GET the updates URL at the end of the previous result, +`http://localhost/api/alarms/updates?id=306`. Because the alarm is no +longer active we do not have a link for force-processing it; instead we +have links for processing it normally with or without comments. + +[subs="quotes"] +---- +"updates": [ + { + "href": "http://localhost:8904/api/alarms/306", + *"id": "306"*, + "time": "2016-11-10T14:21:41", + "message": "Front door has been forced.", + "source": { "name": "Front door" }, + "type": "Forced Door", + "priority": 8, + "state": "unacknowledged", + *"active": false*, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/306/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/306/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/306/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/306/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/306/process" }, + *"process": { "href": "http://localhost:8904/api/alarms/306/process" }* + } +], +"next": { "href": "http://localhost:8904/api/alarms/updates?id=306.1" } +---- + +==== Updating after cutting power + +This is the result of `http://localhost/api/alarms/updates?id=306.1` (the `next` link from the +previous results) after cutting power to the controller and waiting a minute for Command Centre to +raise an alarm about it. Nothing has changed on the previous alarms so they do not come out. + +[subs="quotes"] +---- +"updates": [ + { + "href": "http://localhost:8904/api/alarms/308", + "id": "308", + "time": "2016-11-10T14:35:21", + "message": "Controller \"Fat controller\" Offline.", + "source": { "name": "Fat controller" }, + *"type": "Controller Offline"*, + "priority": 6, + "state": "unacknowledged", + *"active": true*, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/308/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/308/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/308/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/308/acknowledge" }, + "forceProcess": { "href": "http://localhost:8904/api/alarms/308/process" } + } +], +"next": { "href": "http://localhost:8904/api/alarms/updates?id=308" } +---- + +==== Updating after restoring power + +Next we GET `http://localhost/api/alarms/updates?id=308` (the `next` +link from the previous results, again) after restoring power to the +controller and waiting for it to come online. + +The 'controller offline' alarm (ID 308) has changed to inactive since +the controller has reappeared on the network. + +The 'low power' alarm has arrived from the controller carrying a timestamp from when it lost mains +power, while it was running on internal reserve power, which was one minute earlier than the +'controller offline' alarm. + +Bringing up the rear is another alarm that the controller generated when +it restarted. + +[subs="quotes"] +---- +{ + "updates": [ + { + "href": "http://localhost:8904/api/alarms/308", + "id": "308", + "time": "2016-11-10T14:35:21", + "message": "Controller \"Fat controller\" Offline.", + "source": { "name": "Fat controller" }, + "type": "Controller Offline", + "priority": 6, + "state": "unacknowledged", + "active": false, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/308/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/308/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/308/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/308/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/308/process" }, + "process": { "href": "http://localhost:8904/api/alarms/308/process" } + }, + { + "href": "http://localhost:8904/api/alarms/310", + "id": "310", + "time": "2016-11-10T14:34:01", + "message": "Fat controller - power low.", + "source": { "name": "Fat controller" }, + "type": "Controller power low", + "priority": 6, + "state": "unacknowledged", + "active": false, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/310/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/310/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/310/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/310/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/310/process" }, + "process": { "href": "http://localhost:8904/api/alarms/310/process" } + }, + { + "href": "http://localhost:8904/api/alarms/313", + "id": "313", + "time": "2016-11-10T14:35:49", + "message": "Controller \"Fat controller\" restarted after power failed.", + "source": { "name": "Fat controller" }, + "type": "Power failed", + "priority": 6, + "state": "unacknowledged", + "active": false, + "division": { "href": "http://localhost:8904/api/divisions/2" }, + "view": { "href": "http://localhost:8904/api/alarms/313/view" }, + "comment": { "href": "http://localhost:8904/api/alarms/313/comment" }, + "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/313/acknowledge" }, + "acknowledge": { "href": "http://localhost:8904/api/alarms/313/acknowledge" }, + "processWithComment": { "href": "http://localhost:8904/api/alarms/313/process" }, + "process": { "href": "http://localhost:8904/api/alarms/313/process" } + } + ], + "next": { "href": "http://localhost:8904/api/alarms/updates?id=313" } +} +---- + +That illustrates how events' occurrence times can be out of order when the devices sending them to +the database are out of sync. + +== Client-side certificates + +First see <<_authentication_and_encryption_certificates>> for what certificates are and the +difference between server certificates and client certificates. + +This section deals with the client certificate check that happens if you +left 'Do not require pinned client certificates' off in the 'Web +Services' property tab of an 8.40 server, or if you entered a thumbprint +into a REST Client item in 8.50 or later. + +The process in <<_the_request_process>> shows that +if you have not disabled client certificate checking, the server does it +after extracting the API key. If the server does not have the client’s +certificate pinned to the REST Client item with that API key, it will +reject the request and raise an alarm: + +.... +A REST connection was attempted with an invalid client certificate +.... + +That alarm will be at the same priority as a controller disappearing off +the network, which should cause a stir, so try not to do it in +production. + +The next two sections should help you decide whether to use the feature. +The sections following those contain sample command lines that you can +paste into a shell on your clients to create client certificates on disk +or in the Windows certificate store. If you receive syntax errors, you +may have an old version of the software (I have had problems with +`New-SelfSignedCertificate` on Windows) or the hyphens may not be +hyphens: they may come through as dashes, which look very similar to us +but not to shells. You may have to re-type them. + +The bold in the sample command lines reduce the protection around your +private key. That may be acceptable in a development environment but for +proper security in a production environment you should omit the bold +parts. + +=== What client certificate checking does + +// You cannot have _emphasis_ in a paragraph following a link. Asciidoctor turns the first +// underscore in the link into an . So, use __emphasis__ instead. + +When not disabled by link:#_the_mysterious_client_certificate_checkbox[the checkbox] in the server +properties, Command Centre requests proof from the client that it has the private key that matches a +public key that the server has configured into it (pinned). A public key is hundreds of bytes so you +don’t want to paste the whole thing into Command Centre’s configuration, and we do not want to +compare all those bytes for each request, so you enter a shorter form instead. It is known as the +certificate’s __thumbprint__ or __fingerprint__ and is a cryptographic hash of the whole +certificate. It is impossible for a client to send a fake certificate with a thumbprint that looks +real. + +Certificates can also contain a chain of trust linking the certificate +back to a trusted authority. A client uses a server certificate’s chain +of trust to check the identity of the server that responded to its +request. It does not work in reverse: servers do not check that part of +a client’s certificate. You would not have pasted the certificate’s +thumbprint into Command Centre if you did not trust it. + +When you enter a thumbprint into a CC REST Client’s property page in the +Configuration Client or leave the checkbox we are covering next +unchecked in the server properties, you are saying that only the caller +who has the matching private key is allowed to use that REST Client. In +other words, the client software must possess two secrets that the +server can verify: the API key and the private key. + +Before getting into why you want all this checking happening, we should +cover how to turn it on. Or, since the product ships with it turned on, +why you should not turn it off. + +==== The mysterious client certificate checkbox + +In 'Server Properties', which you get to in the Configuration Client +starting with the 'File' menu or the top item in your hardware tree, +there is a tab called 'Web Services'. In there, in the box headed by +'Enable REST API', is a checkbox. In 8.40 it was called 'Require pinned +client certificates'. In 8.50 it is called 'Enable REST Clients with no +client certificate'. + +It behaves like this: + +[width="100%",cols="60%,20%,20%",options="header",] +|=== +|8.40 |'Do not require pinned client certificates' _off_ |'Do not +require pinned client certificates' _on_ +|Connection attempt to a REST Client item _with_ a certificate +thumbprint configured |Client certificate checked |Client certificate +ignored, connection accepted + +|Connection attempt to a REST Client item _without_ a certificate +thumbprint configured |Connection rejected |Connection accepted +|=== + +In 8.50, the top-right quadrant changed. + +[width="100%",cols="60%,20%,20%",options="header",] +|=== +|8.50 |'Enable REST Clients with no client certificate' _off_ |'Enable +REST Clients with no client certificate' _on_ +|Connection attempt to a REST Client item _with_ a certificate +thumbprint configured |Client certificate checked |*New in 8.50: client +certificate checked* + +|Connection attempt to a REST Client item _without_ a certificate +thumbprint configured |Connection rejected |Connection accepted +|=== + +That change will have negatively affected sites that had 'Do not +require…' turned on, but also had thumbprints (uselessly) configured +into their client items. However there is a huge upside to the change: +in 8.50 or later a site can have some clients using client certificates +and some not. + +=== Why use client certificates? + +To make it harder for an attacker to masquerade as a legitimate REST +client. + +To do that, they must obtain your API key at the very least. There are +more barriers that you can put up: + +[cols=",",options="header",] +|=== +|If you: |…the black-hat will then have to: +|use a firewall (Windows or hardware) |be on the server network. +|use an IP filter |spoof the source IP. +|pin your client’s certificate |have a copy of the client’s private key. +|limit your application privileges |settle for less access. +|=== + +Pinning a client certificate is one more hoop an attacker has to jump +through. + +You should make viewing your private key very difficult for anything +that does not need it. Do not leave it in the filesystem for anyone to +read! If you are running on Windows, you should use the certificate +store. If you are on another O/S, protect the key while it is on disk +with filesystem permissions and by encrypting it with a password hidden +in your application. + +=== Create a certificate and record the thumbprint in CC + +The following commands create a client certificate. You need to run them on the +system that will be running your REST client. That may not be your Command +Centre server. + +==== Using OpenSSL tools + +This method works equally well on Unix-like or Windows systems with OpenSSL +installed, but the later sections might serve Windows people better because +they show how to put the certificate directly into the Windows certificate +store. + +WARNING: The OpenSSL commands in this section put the private key on disk, +which should make you a bit nervous if you are doing it in production. On +a Unix-like system you could do it in a mode-0700 folder on a filesystem +that is not backed up and is cleared during a reboot, such as `/tmp`. + +[subs="quotes"] +---- +openssl req -x509 \ + -newkey rsa:4096 \ + -sha256 \ + *-nodes* \ + -keyout rest.pem \ + -out rest.pem \ + -subj "/CN=RESTtest" \ + -days 3650 +---- + +Notice the `rsa:4096`: it produces a four-kilobit key, which is huge. It might be +overkill for development, but it's nice to have the option. + +Its name will be 'RESTtest'. You should come up with a name related to your application's purpose +because diagnising certificate problems is a tough enough job without ambiguous certificate names +muddying the waters even more. + +Again, the bold part is reducing your security. In this case, +the `-nodes` option (footnote: it means 'no DES'. It is not the plural +of 'node') means there is no password on the private key. Anyone could +read it from the `rest.pem` file, so in a production environment you should omit +the `-nodes` option and type in a password (a really good one) when +`openssl req` prompts you. + +To get the thumbprint for Command Centre: + +[source,console] +---- +$ openssl x509 -fingerprint -in rest.pem -noout +---- + +If you protected the PEM with a password, `openssl x509` will ask you +for it. + +NOTE: The 20 bytes that come out are what you paste into the REST Client +item in Command Centre. + +Now you need to add the certificate to the clients that need it. If you use Postman, +see <<_use_the_certificate_in_your_client>>. If you use +Chrome on Windows, you need to add it to the certificate store with +these two commands: + +[subs="quotes"] +---- +openssl pkcs12 -export -in rest.pem -out rest.pfx -passout pass: +explorer rest.pfx +---- + +The first command converts the PEM file into a file format that Windows +prefers. The `-passout pass:` option means it will not put a password on +it, so it is just as dangerous as the PEM file. + +The second line will open `rest.pfx` in Explorer (the same as +double-clicking it) to import it into the certificate store. The default +options are good so you can click Next to let it use the current user, +determine the certificate store automatically, and mark the private key as +not exportable). + +Finally, for goodness’ sake, protect the `rest.pem` and `rest.pfx` +files. Preferably delete them. Even better, use an eraser utility. + +==== Using Windows tools + +If your client is on a Windows host there are two more ways to create a +certificate and place it into Windows’s certificate store. Obtaining the +private key from there is easy for your client program but difficult for +anyone else, Microsoft assures us. + +===== Powershell + +The topic 'Creating the Client Certificate' in the Configuration +Client’s online help contains instructions for doing it in a PowerShell +with `New-SelfSignedCertificate`. Handily, it prints the thumbprint to +the console so you can copy it into Command Centre. You should try that +first, since it is simplest. But `New-SelfSignedCertificate` is not +present on all versions of Windows, so here is an alternative using… + +===== makecert + +…which has been around for longer. + +[arabic] +. Run a developer command prompt as administrator. If you do not have a developer command prompt, + try a regular command prompt (as administrator). +. In it: + ++ +[,console,subs="verbatim,quotes"] +---- +makecert ignoreme.der + -a sha1 + -ss My + -sky signature + *-pe* + -len 2048 + -n "CN=RESTClientCert" + -sr CurrentUser +---- + ++ +That will create a certificate and place it in your certificate store +with a copy on disk. + ++ +Its name will be 'RESTClientCert'. As previously indicated, you should come up with an informative +name for your certificates so that the brave souls diagnosing your TLS issues have something to +go on. + ++ +The `-pe` marked the key as exportable. More on that later. + +You do not need to keep the file `ignoreme.der`, but the easiest way to +get the thumbprint of your new certificate is to open `ignoreme.der` by +double-clicking on it in Explorer, go to the Details tab, scroll to the +bottom, and click the thumbprint. You could then skip the next three +steps, but when starting out it is a good idea to perform these steps to assure yourself +that `makecert` put your new certificate where it should have. + +[arabic, start=3] +. Run `mmc`, add the Certificates snap-in to manage "My user account," +open it and then expand your "Personal" certificates. +. Ensure you can see a certificate called (issued to) 'RESTClientCert' in there. +This is the cert you will pick for your browser later. +. Double-click it, go to the Details tab, scroll to the bottom, and +click the thumbprint. + +NOTE: Those are the 20 bytes / 40 characters that you paste into the REST Client item in Command Centre. + +===== Aside: other ways of calculating the thumbprint + +For your information, here are three more command-line options for +extracting the thumbprint from the DER file if you didn’t get it above. +They all do the same thing. Use whichever works for you: + +// The 'shell' parser would be good but it chooses to highlight 'sha1sum'. +.... +openssl x509 -in ignoreme.der -inform der -noout -fingerprint +openssl sha1 ignoreme.der +sha1sum ignoreme.der +.... + +As you can see from the last two, a certificate thumbprint is really +just the SHA1 hash of the certificate when it is stored in a DER file. + +CAUTION: Some utilities, inluding openssl, print a thumbprint with colons separating each octet. +Take the colons out before pasting that into the REST Client item, otherwise your client will not be +able to make calls through the cloud API gateway. + +===== Marking keys as exportable + +The `-pe` option to your `makecert` command above marked your private +key as exportable. + +You can mark it as not exportable so that the standard utilities will not +be able to get it out of the certificate store. That sounds like a good +idea, because (while there are programs out there that export +non-exportable certificates) anything you can do to make the black-hat’s +job harder is a win. + +If you used the command `New-SelfSignedCertificate` in Windows +Powershell (using the instructions in the Configuration Client’s user +guide), you can mark the certificate not exportable by adding +`-KeyExportPolicy NonExportable` to the command line. + +The trouble is that in order to use your client certificate in Postman, +you have to export the key. + +If you used `makecert`, remove the `-pe` and your new key will not be +exportable. + +==== Extract the certificate and private key from the Windows store to disk + +If you are going to use Postman you need to give it files containing +your private key and certificate, but if you are on Windows and used one of the Windows +utilities to create a new certificate in the certificate store, +you will not have the private key on disk. You will need to extract it. + +[arabic] +. Run mmc. Certificates -> Current User -> Personal -> Certificates. +. Right-click your certificate -> All tasks -> Export… + +Select the option to export the private key. Give it a password, +otherwise openssl cannot decrypt it. + +It does not matter what you do with the other certificates, so leave the +defaults set. + +Export it to a `.pfx` file on disk. ++ +That PFX file is partly secure because you put a password on it, but I expect +that password was very short so, again, be careful what you do with that +file. +. For old versions of Postman you may have to convert that PFX into a +file it understands. The current version of Postman does not need this: ++ +[,console,subs="verbatim,quotes"] +.... +$ openssl pkcs12 -in restexported.pfx –out rest.pem *–nodes* +.... + + +It will ask you for the password you picked for the export. It will put +the certificate and the private key in the PEM file, unencrypted +(because of `-nodes`). It is plain text: you can look at it in Notepad. + +==== An easy (but not so secure) way to discover your client certificate’s thumbprint + +Create a client certificate using one of the methods above and use it in +an API call. The server should raise an alarm, complaining that 'a REST +connection was attempted with an invalid client certificate'. The rest +of that message will tell you which REST Client Item you need to put the +thumbprint on, and the alarm's details string will contain the thumbprint +itself. + +If the thumbprint is `(null)`, your client did not send a certificate +at all. + +Otherwise, copy the thumbprint straight out of there and paste it into +the item. The next time you try your call the server should not complain +about the certificate. + +The reason this method is not so secure is that you might not be sure that +the alarm was yours. Someone else may have hit the API before you did. + +=== Use the certificate in your client + +==== Postman + +The standalone version of Postman cannot read certificates out of the +certificate store (footnote: the Chrome extension can, but that version of +Postman is no longer under development). + +Go to the cog -> Settings -> Certificates. Add the certificate that +Postman should use when talking to your server and port. Where Postman +asks for the CRT file, give it the file containing the certificate. Where +Postman asks for the key file, give it the file containing the private key. +They will both be the same file if you followed the example above. + +If you protected your private key with a password (a good idea, but turned +off by `-nodes`), give it to Postman. + +image::postman_client_cert.png[Tell Postman which client cert to use,title="Tell Postman which client cert to use"] + +Now Postman will use that certificate when it talks to Command Centre. +If you put the certificate’s thumbprint on the REST Client item with the +API key Postman is using, you can turn on pinned certificates in the +server properties and Postman will still be able to connect. + +You can leave Postman using this certificate no matter whether CC has +pinned certificates turned off or on: it does no harm. + +==== Chrome + +When you first try to connect to Command Centre using Chrome it will +give you a list of certificates in the store and ask you which to use. +Select the one you just put there. + +==== wget + +[,console,subs="verbatim,quotes"] +---- +$ wget \ + *--no-check-certificate* \ + --certificate=_your_pem_file_ \ + --header="Authorization: GGL-API-KEY _your-API-key_" \ + https://localhost:8904/api +---- + +The `--no-check-certificate` turns off client-side checking of the +server certificate. + +Careful: my version of wget does not complain if it cannot read the +certificate file. + +==== curl + +[,console,subs="verbatim,quotes"] +---- +$ curl \ + --verbose \ + *--insecure* \ + --cert _your_pem_file_ \ + --header "Authorization: GGL-API-KEY _your-API-key_" \ + https://localhost:8904/api +---- + +The `--insecure` turns off client-side checking of the server +certificate. + +I found `--verbose` necessary to see error codes. + +== Server-side certificates + +A client in a production environment should refuse to talk to a server that it does not recognise. +"Recognising" a server means either + +- having a copy of its certificate on the client already (which is the same as the client + certificate pinning covered in the previous section, only it is the client doing it to the server + instead of the server doing it to the client), or +- having a trusted Internet authority's signature on the certificate. + +WARNING: *Production clients should check server certificates.* If not, an attacker could +masquerade as the server. The client would send it its API key to the fake server, and the attacker +could--if the real server was not checking client certificates--use it for calls of its own. + +But if you are working in a development rig and your server does not have a signed certificate, you +might like to tell your client to skip the server certificate check, as you told Chrome and Postman +to do earlier. + +If you will pardon a very brief dive into source code, here is one way to do it in a C# client: + +[source,csharp] +ServicePointManager.ServerCertificateValidationCallback = delegate ( + object s, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { return true; }; + +If you do not have control of the client application, or you wish to check the server certificate +(which is advisable), here are a couple of approaches to making the check succeed. + +You only need one of these. + +=== Pin the server certificate on the client + +Go to Server Properties in Command Centre's configuration client, then Web Services -> Manage +Certificates, and click View on whichever type of certificate you are using. Go to the Details tab +in the window that appears, then choose Copy to File. You have a few choices for the format to +export it to. DER is good for Windows machines, but a PFX--if that option is enabled--might be +more widely accepted on non-Windows systems. If you are ever asked if you want to export the private +key, _just say "no"_. + +Once you have your certificate file on disk, copy it to the client +machine. Provided you didn’t let a private key get in there, it is not a +secret. + +How you install it on the client depends on the client. On a Windows +box, it may be as simple as double-clicking it. The Certificate Import +Wizard will ask you where to install it: you probably want Trusted Root +Certificate Authorities. That is a bit of a sledgehammer, because it +makes not only your REST client but every client on that host trust that +certificate. Plus, they will trust any other certificate signed by it. +But it will get you going. + +You have more options if you are writing your own client. You might like to have a copy of the +server's certificate on disk to compare against. A certificate is large, so the usual approach is +to record a hash of it instead. It is not a secret but you must prevent anything from changing it, +because when your client connects to the server you want to hash the server's certificate again and +compare it to the value you have on record. If they are not the same, don't trust the server. + +Windows calls the SHA1 hash of certificate its _thumbprint_, and presents it in the Details tab of +the window the first paragraph took you to: Server Properties -> Web Services -> Manage +Certificates -> View -> Details. You'll find the thumbprint at the bottom of the field list (you +will have to scroll). + +=== Buy a signed certificate for the server + +If that method does not +suit, perhaps because you do not have control of the clients, you could +buy a "real" certificate for your CC server. One drawback is that +signatures eventually expire, requiring you to do this every year or +three. Another is that you must buy a new certificate if you change the +name of your server. So, use a DNS alias. + +This is nothing new for REST APIs: it is the process that every web +site owner goes through. If you want to sell scones and Toby mugs from +www.itsabritishthing.com, for example, you: + +. generate a certificate for www.itsabritishthing.com, +. generate a certificate signing request (a CSR) from that certificate, +. decide which signing authority to use, +. send the CSR to that signing authority, with your credit card details and proof that you own + itsabritishthing.com, +. wait for them to send you your new certificate, then +. install it on your web server. + +That is exactly what you will need to do for Command Centre. Start with +the DNS alias for your server instead of www.itsabritishthing.com. The +next few steps are a Googling exercise for the reader because they +depend on which authority you choose. Finish with a simple process +covered in the section called "Replacing the web service certificate" +of the Configuration Client’s user guide. Briefly, it is: + +. Go to Server properties -> Web Services -> Manage Certificates in the REST API section, +. change the radio box to Custom Certificate, +. click Import, and +. browse to your certificate file. + +At time of writing, Gallagher Group has no association with +www.itsabritishthing.com. + +== Other Command Centre items + +Version 8.00 added fence zones, access zones, alarm zones, doors, +macros, and outputs (relays and LEDs) under two licences. The RESTStatus +licence lets you see their status and some basic configuration, and the +RESTOverrides licence lets you send overrides to them. So you can open +doors, run macros, disarm fence and alarm zones, toggle outputs, and so +on. Version 8.10 added inputs, which are often connected to physical +devices such as reed switches and infrared sensors. Version 8.30 added a +method `/api/items/updates` which lets you monitor several items with +one connection. Version 8.50 added operator groups, schedules, and day +categories. + +Note that the REST API does not let you create, edit, or delete any of +these items except schedules. The configuration client is still the +place for that. + +Each of those item types has its own controller, its own block of links +in `/api`, and its own section in the developer documentation. +Schedules, day categories, and the items with status like zones and +hardware have their documentation under "Status and Overrides". +Because the `items/updates` method is on the items controller it in with +Alarms and Events. Operator groups are in with cardholders. + +As an example of how to use these APIs, here is how you would list all +your doors and get the link to open one of them: + +.... +GET /api +// You would find and use the URL at features.doors.doors.href, which in 8.30 is: +GET /api/doors +// To search for one door use the 'name' parameter: +GET /api/doors?name="Greendoor" +// But if all you intend to do is open it, you only need the override links: +GET /api/doors?name="Greendoor"&fields=commands +.... + +=== Overriding items + +To send an override to an item you make an HTTP POST to a URL that you +get from the item itself. + +The output from the last example above, that requested the `commands` +block of the door called 'Greendoor', is: + +[,] +---- +"results": [ + { + "commands": { + "open": { + "href": "https://localhost:8904/api/doors/507/open" + }, + "free": { + "href": "https://localhost:8904/api/access_zones/533/free" + }, + "freeUntil": { + "href": "https://localhost:8904/api/access_zones/533/free" + }, + "freePin": { + "href": "https://localhost:8904/api/access_zones/533/free_pin" + }, + "freePinUntil": { + "href": "https://localhost:8904/api/access_zones/533/free_pin" + }, + "secure": { + "href": "https://localhost:8904/api/access_zones/533/secure" + }, + "secureUntil": { + "href": "https://localhost:8904/api/access_zones/533/secure" + }, + // ... ten more commands omitted for brevity ... + "cancel": { + "href": "https://localhost:8904/api/access_zones/533/cancel" + } + } + } +] +---- + +Normally a search would return an ID, href, and name, and it would not +return that block of commands, but we turned that on its head by using +the `fields` query parameter to request the `commands` block and nothing +else. Each of the objects inside it is a named command containing an +href which, when you POST to it, sends an override to the item. For +example, if that was a door on your system and you pasted the URL from +`commands.open` into Postman, and POSTed it, the unlock relay on that +door would fire. + +Each item type has a different set of commands you can send it. They +vary in type and number: outputs have four and access zones have 21. +Most of those access zone overrides are also available on the zone’s +doors, for convenience. + +Overrides don’t need anything in the body of the POST, but those with +'Until' in the name of the command will use a timestamp if you send it: + +[,] +---- +// POST /api/access_zones/533/free +{ + "endTime": "2020-03-06T00:00:00Z" +} +---- + +That example would put the door’s entry access zone in free mode until +midnight March 6. + +=== Status flags + +Just as each item type has its own commands, each also has its own set +of status flags. A door can be open or closed, for example, while an +access zone can be secure or free. Each also has its own set of flag +rules that they will always follow. Doors, inputs, and outputs are quite +simple but fence zones have half a dozen rules thanks to the voltages +they deal with. + +The developer documentation clearly lays out all the status flags items +can return, and their rules. For example, here is part of the section on +outputs: + +____ +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` means the output is under the effect of an override. + +If and only if the output is online, one of `relayStateUnknown`, +`closed`, or `open` will appear. Of the above, only `overridden` can +appear when the output is offline. +____ + +The above tells you that the first flags you should look for are +`relayStateUnknown`, `closed`, and `open`. If none of those is in the +flag set then your output is offline. Other flags will tell you what the +problem is, if you want to go deeper, but it is probably enough for your +integration to know that the output’s state is uncertain and it should +subscribe to updates in case that changes. + +At last count there were eleven status flags common to all items. Some +are not so serious, like the flags that indicate the item is shunted +(muted) or is not fully configured yet. Others indicate an actual +problem like a network outage, a cable fault, or a service not running. +The developer documentation covers them all (search for 'abnormal +status'). + +There is a lot more on the topic in the reference documentation. + +=== Subscribing to updates (one item at a time, pre-8.30) TODO + +This section TODO. What follows is a broad outline of what the section +should contain. + +GET the `updates` link on an item’s details page. It is a long poll, so +the server won’t respond until it has something for you (or it times out +after about 50s). Then stay up to date by entering a loop GETting the +`next` link. + +You really should be using bulk item updates, in the section below, because this method requires one +TCP connection per monitored item. Why use this? + +* You’re not running 8.30 yet, or +* you’re monitoring only one item and want a slightly simpler call, or +* your client wants to wait longer than 30s between GETs (but it is hard + to imagine a client needing that). + +=== Subscribing to updates (many items, 8.30+) TODO + +A rough outline: GET `/api` then POST a document to the link at +`features.items.updates` (which is `/api/items/updates` in 8.30, but may +change, which is why you should use the page at `/api`). + +The body of your POST should look like this: + +[,] +---- +{ "itemIds": ["508", "526"] } +---- + +Those numbers are item IDs. Even though they look like integers they +must be in quotes because in the future they could contain alphas. Place +as many in the array as you like. We tested 1,000 without it affecting +performance. + +The POST will return with the status of all your items and a next link. +GET that link, and keep GETting it in a loop to stay up to date. The +calls will block if there are no changes to report. Sleep between calls +to avoid tight loops. But not longer than 30s, otherwise the server will +drop your session, thinking you have walked away. + +The first GET will return the same states that the POST did, which seems +redundant, but that is just the way it is. Just keep looping. + +This is a far better way of monitoring multiple items that the previous +because it only uses one server connection per client. + +At time of writing (8.80) this is the only API call that maintains state +between calls. All the others require their context to arrive with the +query. + +=== Schedules and day categories TODO + +Schedules arrived in 8.40. The API lets you view, create, and delete them, and +modify how they affect their items. It does not let you change the items +they affect. + +[TIP] +==== +If you are not familiar with Command Centre: + +There are seven types of schedule. They all contain a list of day +categories (more on those later), and for each day category, a list of +times (00:00 through 23:59) paired with an action to perform at that time. + +Where the schedule types differ is in the item types they can control, and +the actions that they trigger on the item they are controlling. + +[cols="30%,70%",stripes=even] +|=== +|Schedule type | Actions +|Cardholder | Access granted / denied +|Access zone | Zone mode (secure, free, etc.). +|Alarm zone | Zone mode (armed, disarmed, etc.). +|Output | On / off / cancel overrides +|Notifications | On / off +|HV/LF | High / low / cancel overrides +|Elevator kiosk | Control mode +|=== + +The access zone and alarm zone schedules can switch their zone to any mode. +The other five select between two modes. + +Some of them have another action that cancels any untimed overrides that +happened since the last scheduled changed, returning control back to the +schedule. It won't affect overrides that have an end time. + +==== + +When updating a schedule you must replace its entire list of day +categories and times. This is quite different from how you update other +lists via this API. The JSON schema has room for us to accept the normal +style as a future enhancement, but in 8.40 you must replace all a +schedule’s schedules at the same time. That suits our target use-case, +which is for an integration to download the schedule, de-serialise it +into an object model, change some part of it, then re-serialise and +upload it back. + +As of 8.80, the only types of item that the API can create are +cardholders, visits, and schedules. + +== Visitor management + +=== Introduction + +The Command Centre Visitor Management feature allows you to manage _visit_ items. A visit contains + +* a list of expected _visitor_ cardholders, +* a _host_ cardholder, responsible for a visitor while on site, +* a _reception_, which is a location at which visitors may arrive, +* a _visitor type_, that serves two purposes: an access group that Command Centre will add + your visitors to when you add them to the visit, and an index into more visitor management + configuration, +* a list of _visitor access groups_ that your visitors need to be in while on site, +* other flat fields such as the dates of the visit and a description. + +The host is person who Command Centre should notify when a visitor +signs in. If a visitor does not have a card, tag boards and reports will +show them in the same access zone as their host. A cardless visitor +appears to follow his or her host around the site, in other words. + +The visitor type (an access group) is there so that visitors can have +PDF values assigned before they arrive. This is useful for aids to +identification such as photos and driver’s licence or passport numbers. +Recall that in order to hold a value for a PDF, a cardholder must be in +one of the PDF’s access groups. + +The visitor access groups (not the visitor _type_ access group) are +there so that after a visitor signs in they can open doors using a card. +No access groups: no access. + +=== Capabilities of the API + +The API lets you read the relevant parts of visitor management +configuration, list receptions, and CRUD visit items, including their +visitors. + +It does not let you change visitor management configuration. You do +that on divisions in the Configuration Client. + +v8.90 lets you sign visitors in or out and mark them on or off site. + +=== Setting up Command Centre to use Visitor Management via the API + +To use the Visitor Management feature, you must: + +* add some visitor management configuration to a division in the Configuration Client, including at + least one visitor type (access group) with at least one _host type group_ and _visitor access + group_, and +* create at least one reception item in the same division, again in the Configuration Client. + +Your software can then use the API to get that division configuration +and the list of receptions. When it creates a visit it will need to +comply to the rules in the division configuration, including: + +* the visit’s visitor type access group must be in the same division as the visit’s reception, +* the visit’s visitor type must be one of the division’s visitor types, +* the visit’s host must be a member of at least one of that visitor type’s host access groups, +* the visit’s visitor access groups must be a subset of that visitor type’s visitor access groups. + +The division in question is the visit’s reception’s division. + +Command Centre reevaluates its rules every time you change a visit. We +aimed to provide useful error messages, so if you receive anything +except a 200-level response code please look in the body. + +When you add a visitor to a visit, Command Centre will add that +cardholder to the visit’s visitor type access group. Then you can add +PDFs to the cardholder (such as an image). When the cardholder signs in +using a Gallagher Visitor Management Kiosk, or when a greeter signs them +in using the Visitor Management Client, Command Centre will add the +visitor to the visit’s visitor access groups. + +== The API gateway + +The API gateway lets clients connect to the server when they cannot reach its port 8904 directly. +Command Centre needs to be connected to a cloud server, and it needs to turn on the API gateway +globally and in each REST Client item that needs it (which only takes a few clicks) then the client +simply changes the host and port in its connection URL to the cloud server that the Command Centre +server is connected to. That is most likely to be one of these two: + +[cols="1,3"] +|=== +| Australia +| commandcentre-api-*au*.security.gallagher.cloud + +| United States | commandcentre-api-*us*.security.gallagher.cloud +|=== + +The port (8904, unless you changed it from the factory setting) also has to go because the gateway +listens on the default port for HTTPS, 443. + +Everything else is the same, including the API key, the client certificate, the root path (`/api`), +and the documentation URL https://gallaghersecurity.github.io. + +// TFS 119104: there is a bug that hides source IP numbers from us when the request +// comes through the gateway. The source IP will be one of Amazon's load balancers, 10.218.x.x. + +The request's source IP number will _not_ be that of your client host by the time the request +arrives at our gateway. It will be the internal address of one of Amazon's load balancers, so you +will need to untick 'Enable IP Filtering' when you move from the Command Centre server to the API +gateway. + +There are no additional licence requirements for the API gateway. + +Note that due to the traffic hairpinning through Amazon's servers, throughput is significantly +reduced. + +For a lot more detail about the API gateway, see its technical information paper at +https://gallaghersecurity.github.io/docs/Command%20Centre%20Cloud%20Api%20Gateway%20TIP.pdf + +== Notes for penetration testers + +This section contains answers to questions raised after penetration tests. + +Also see <<_the_request_process>> for how the server authenticates and authorises requests. + + +=== Item IDs are easily predicted + +All Command Centre's items are referenced by a URL containing the item's database ID, which is a +small integer. It is trivially easy to generate the URL ("href") of every item in the system by +starting at ID number one and iterating up. Some tests flag this as an issue. + +We expect attackers to do this. We do not rely on a client's ignorance of server state to protect +it (other than API keys, of course). The server will always enforce the licensing and privilege +models. For example, when a client asks for the details of an item it is unable to view the server +will respond as though the item does not exist (with a 404). + +The external penetration testers Gallagher engages have access to our source code so that they can +conduct white-box testing and fully exercise the privilege model. Their reports are available on +request. + +=== There is no rate-limiting of authorised requests + +The API does not throttle correctly-authenticated API requests, which makes it look like it is not +protecting itself from denial-of-service attacks. + +It protects itself from incorrectly-authenticatd API requests. It raises medium-priority alarms for +the first few bad requests, then another at the highest priority to inform operators that it is +under a DoS, after which it falls silent until the attack ends. + +We are looking at options for rate-limiting the API, but it is a balance between protecting +the server from an unlikely situation and preserving the normal operation of our integrations, some +of which make a huge number of requests per second. + +We consider a DoS attack from a fully-authenticated bad actor (in other words, one with a valid API +key, TLS certificate, and source IP) is not likely enough to justify urgency. + +So far, the denials of service our customers have observed were caused by authenticated integrations +acting improperly. To help developers avoid that the reference documentation contains sections +headed "efficiency tips". + +=== The server uses the Host header + +Our web server uses the Host header sent by the client to form URLs that it sends back. Although +the server does not change its behaviour based on the header's value, using it to build strings is +enough to cause a negative result on some tests. + +A client sending a malformed Host header will only affect itself. This would be a problem if an +attacker was modifying (poisoning) a valid request on its way to us, but if the attacker could do +that it would have observed a valid API key and TLS certificate, plus it would be able to pollute +our responses on their way back to the client, so it would hardly bother with this attack. + +This class of vulnerability is a problem when there is an HTTP cache between client and server. +This is never the case for program interfaces. + +== Monitoring the API + +The web server presents its vital statistics in Windows performance counters, and (optionally) +writes every API call to a log file. + +=== Performance Counters + +Every API call increments a Windows Performance counter called 'REST API Request Rate'. You can +find it, along with other performance counters updated by Command Centre's other API servers, in a +category called 'Gallagher Command Centre'. That counter will give you a view of how many calls the +API is receiving. + +==== Per-client performance counters + +If you are running 8.90 or later, another category called 'Gallagher Command Centre REST clients' +contains counters that will show you which of your clients are hitting the API the hardest. They +are called 'Requests per second' and 'Total processing time (ms)' each with an _instance_ for each +REST Client item in Command Centre. + +TIP: An _instance_ of a performance counter is one created at run-time to suit to the environment. +They are the lowest tier in the hierarchy: category, counter, instance. + +In our case, the environment is a pool of REST clients so CC creates an instance for the REST Client +item the first time an API call arrives for that item (i.e., the first time an API call arrives with +that item's API key in its Authorization header). + +NOTE: The instance takes the name that the client item had when the session was first created, which +is also when all its privileges were calculated. If you rename a REST Client item and notice that +no new performance counter appears, that's why. Click on the 'Refresh Operator privileges' button +in the item's configuration page if you need it to update smartly. + +Also note that some characters are not permitted in the name of a performance counter, so the server +may have adjusted them to suit. + +==== Per-route performance counters + +The 'Gallagher Command Centre API routes' category, also added to 8.90, will show you which API +routes (a.k.a. endpoints) are the most popular. It contains counters called 'Requests per second' +and 'Total processing time (ms)' again but this time each has one instance for each API route. + +Only routes that a client has called will have a counter. + +Like client names, API routes have their URLs heavily modified to become suitable performance +counter names and to make them more readable. + +==== A word of warning about processing times + +The processing time that comes out of these counters for an API call does not necessarily reflect +the CPU or I/O cost of that call. It is simply the difference in wall-clock times before and after. +If the operation the client requested had to wait for a resource, such as a lock, then the call will +look expensive even though it spent part of its time in a queue. + +Therefore these processing times only represent the true cost of an operation when the server and +database were able to give it their full attention. + +=== Access log + +Servers 8.90 and later write a line to an access log for every API request. + +It is controlled by a DWORD value called `LogRESTRequests` inside the registry key +`HKLM\SOFTWARE\WOW6432Node\Gallagher\Command Centre`, set to one by the installer. Change its value +to zero to stop the logging. You do not need to restart Command Centre. + +9.00 servers and later truncate URLs if they are too long. This is controlled by a DWORD in the +registry called `LogRESTURLMaxLength`: if a URL has more characters in it than that, it will be +truncated. The default is 200. Zero suppresses URLs altogether. If you want your URLs to go into +the log unmolested, set it to a high value (such as four billion). + +The default file name is `RESTAccess.log` and the file will appear in the same folder as the +server's licence and other logs. NLog will rotate it once per day by adding a time stamp to its +name. After ten days it will delete it. There is no limit to the size of the file. Those settings +are in the server's application config, but we advise against changing them as doing so may affect +our ability to offer technical support. Also, your changes may not survive an upgrade. Also, a +syntax error in the app config will prevent Command Centre from starting. + +The default file format is JSON, for easy consumption by the popular log aggregation platforms. +Each request will be on one line. Here is an example busted out and indented for easier reading: + +---- +{ + "request_time": "2022-12-02T03:41:52.5732150Z", + "ip": "127.0.0.1", + "client": "REST Client item name", + "method": "GET", + "url": "/api/events?top=9999", + "status": 200, + "elapsed_ms": 502, + "endpoint": "api/events" +} +---- + +`request_time` was the time when the API call arrived, but the log line goes to disk when the call +finishes, so it will not always be an increasing value. A long poll, for example, may be logged 50 +seconds after it was requested, by which time many other (quicker) calls may have started, finished, +and been logged. + +`endpoint` will be null (missing, actually) if the web server couldn't find a route to service the +request. The status of that will be a 404. Somebody didn't read link:#_the_importance_of_api[about the importance of `/api`]. + +The format of this file, plus its name and rotation policy, are yours to configure via the +application config file. There is an example in there of how to produce a format closer to the +Apache common log format. However: + +CAUTION: Contact Gallagher Technical Support with your wishes before editing Command Centre's +configuration file. It is large and complex and if you get it wrong, the server will not start. +Worse, it may start but then not behave as you expect. + +== Appendix: Privilege table + +This is not the complete list of privileges! See the topic 'Which Operator Privileges +you require' in the Configuration Client’s online help for more. + +Remember that privileges lie on divisions, not on items, so when this +table says you need a privilege on some item, take it to mean that you +need that privilege on the division containing that item, or (because +every division inherits the privileges of its parent) one of that +division’s ancestors. + +'Advanced User' grants nearly all privileges, so I don't mention it here. + +[cols="30%,70%",stripes=even] +|=== +|Goal |Privileges required + +|View cardholder data at `/api/cardholders` and `/api/cardholders/id` except notes and operator +fields. +|'View Cardholder' or any of the privileges that allow editing a cardholder, on the cardholder’s +division. + +|View all cardholder data +|'View Cardholder' or any of the cardholder editing privileges on the cardholder’s division, plus +'View Cardholder Notes' reveals notes, and 'View Lockers and Assignments' adds locker detail. + +|View cardholder PDF values +|'View Cardholder' or any of the cardholder editing privileges on the cardholder’s division, plus +your operator group needs to give you 'view' or 'edit' access to the PDF, or the PDF's default +privilege must be 'view' or 'edit'. + +|Create cardholders, but not modify them. +|'Create Cardholders' on the cardholder’s division. + +|Create and edit cardholders, except their notes and operator settings. +|'Create and Edit Cardholders' on the cardholder’s division. + +|Edit cardholders, except their notes and operator settings. +|'Edit Cardholders' on the cardholder’s division. + +|Edit cardholder notes. +|One of the privileges that lets you edit cardholders as well as either of 'Add Cardholder Notes' or +'Edit Cardholder Notes' on the cardholder’s division. It is different in the thick clients: there, +one of the last two is enough. + +|Modify cardholder access group memberships. +|One of the privileges that lets you edit cardholders on the cardholder’s division plus 'Modify +Access Control' on the group’s division. 'Modify Access Control' on the group’s division is enough +in the thick clients. + +|Change a cardholder’s location. +|'View Cardholder' on the cardholder, and 'Manage Cardholder Location' on the target access zone’s +division, when you are moving the cardholder into an access zone, otherwise any division, when you +are moving the cardholder outside the system. By the way: collecting access zones normally requires +the RESTStatus licence, but there is a variant of that call that returns just the zones your +operator is allowed to move cardholders to that only requires the RESTCardholders licence. + +|Assign a card to a cardholder. +|One of the privileges that lets you edit cardholders ('Create' or 'Create and Edit') on the +cardholder’s division and on the card type’s division. + +|View assignable card types at `/api/card_types/assign` +|One of the 'Edit Cardholder' privileges on the card type’s division. + +|View card types at '/api/card_types' +|'View Site' or 'Configure Site' on the card type’s division. The privileges that let you create or +edit cardholders also reveal PIV types. + +|Change locker assignments. +|A privilege that lets you edit cardholders on the cardholder’s division plus 'Manage +Locker Assignments' on the locker’s division. 'Manage Locker Assignments' on the locker’s division +is enough in the thick clients. + +|Disable a card. +|One of the privileges that lets you edit cardholders on the cardholder’s division. The 'Disable +Card' privilege has no effect on current versions of the REST API. In the thick clients you do not +need edit privileges on the cardholder if you have 'Disable Card'. + +|Change a card's PIN. +|"Allow re-printing and re-encoding" or "Print/preview & encode card" or "Edit cardholders" or one +of the privileges that let you edit a cardholder (which in 8.90 are "Edit cardholders" and "create and edit cardholders"). + +|De-authorise a cardholder. +|'De-authorise Cardholder' or one of the privileges that lets you edit cardholders on the +cardholder’s division. There is a bug in versions up to and including 8.70 that means you need +'Edit Cardholders' to de-authorise a cardholder. 'De-authorise Cardholder' works on its own in 8.80 +and later. + +|Delete a cardholder. +|'Delete cardholders' on the cardholder's division. Not 'Create Cardholders', 'Create and Edit + Cardholder', or 'Edit Cardholder'. + +|Redact a cardholder's events. +|'Delete Cardholder History' on the cardholder's division. + +|Redact a cardholder. +|'Delete Cardholder History' and 'Delete Cardholders' on the cardholder's division. + +|Edit a relationship between cardholders. +|One of the privileges that lets you edit cardholders on the cardholder’s division and on the role’s +division. + +|View a cardholder’s operator configuration +|'View Operators' or one of the privileges that let you modify an operator, such as 'Edit Operators' +or 'Enable/Disable Operator'. + +|View operator groups +|'Edit Operator Groups' or 'View Operator Groups'. + +|Change a cardholder’s operator groups +|'Modify Operator Group Membership'. + +|View PDF definitions at `/api/personal_data_fields` +|'View…' or 'Edit Personal Data Definitions' on the PDF’s division. + +|View events at `/api/events` +|'View Events and Alarms' or any of the privileges that allow processing alarms, on the division of +the event or alarm. + +Or (in 8.70) 'View Cardholder Events' on the division of the cardholder related to the event and on +the event's division. + +|Acknowledge, process, or mark alarms as viewed. +|'Edit Alarms' on division of the event or alarm. + +|Create new events (8.10+) +|'Create Events and Alarms' on the division of the source of the event, if the source is not your + REST Client item, or any division otherwise. Prior to 8.90 having it on any division was enough + (bug). + +|List access groups. +|'View Access Groups' or 'Edit Access Groups' on the access group’s division. + +|List competencies. +|'View Site' or 'Edit Site'. + +|Receive schedule hrefs in an access group +|'View Schedules' on the schedule’s division. 'View Site', 'Configure Site', and 'Edit Site' will +not do it. + +|List access zones and receive their hrefs in other results +|'Edit Site', 'View Site', or 'Override' on the access zone’s division. + +|Override an access zone’s mode. +|'Override' on the zone’s division. + +|List alarm zones +|'Edit site', 'View Site', or 'Override' on the alarm zone’s division. + +|List doors +|'Edit site', 'View Site', or 'Override - Open Door' on the door’s division. + +|Override doors +|'Override - Open Door' on the door’s division. + +|List fence zones +|'Edit site', 'View Site', or 'Maintenance Override' on the fence zone’s division. + +|Override fence zones +|'Maintenance override' on the fence zone’s division. + +|List inputs +|'Edit Site', 'View Site', 'Maintenance Override' on the input’s division. + +|List macros +|'View Site', 'Run Macros', or 'Schedule and Run Macros' on the macro’s division. + +|List outputs +|'Edit site', 'View site', or 'Override' on the output’s division. + +|List day categories +|'Configure Site', 'Edit Schedules', 'View Site'. Day categories are divisionless, so having one of +those privs on any division is enough. 'View Schedules' is true to its word: it will not show you +day categories. + +|List schedules +|'View Schedules', 'Edit Schedules', 'Schedule Access Zone' (though the last one only gives you +access to access zone schedules, not the other five types). + +|Create, edit, and delete schedules. +|'Edit Schedules'. + +|List elevator groups +|'Edit Passenger Details' is probably the one you want. 'View Site' lets you see elevator groups, but +might not let you use them on a cardholder. + +|Set a cardholder’s default floors (for calling elevators) +|'Edit Passenger Details'. + +|Run a macro at '/api/macros/id/run' +|'Run Macros' or 'Schedule & Run Macros' on the macro’s division. + +|Shunt or unshunt an item. +|'Maintenance Override' on the item’s division. 'Override', which is good for most other overrides, +is not enough to shunt or unshunt an item. + +|View a division’s visitor management configuration, and view receptions +|'View Site', 'Edit Site', 'View Visits', 'Edit Visits', or 'Manage Receptions'. + +|View a visit +|'View' or 'Edit Visits'. 'Manage Receptions' will not do it. + +|Modify a visit +|'Edit Visits'. 'Manage Receptions' will not do this either. + +|Modify a division +|'Configure Site' or 'Edit Enterprise Data Interfaces' on the division itself. + +|=== + + + +== Appendix: Features and licences + +=== Alarms and events + +7.80 allows reading and writing unprocessed alarms. Clients can read all +their fields, mark them as viewed, add comments, acknowledge, and +ultimately process them. + +7.80 allows reading events. Clients can see all fields, including these +related items: + +* cardholder, entry/exit zone, division, +* (in 8.00) the source item, and the operator and access group on +head-end events (i.e., those that did not come from a controller), +* (in 8.30) the door on guard tour events, and +* (in 8.40) the item that an operator modified. + +At time of writing (8.30), the following are absent from the list of items related to an +event: locker bank and locker, door (unless it is a guard tour event), +missing competency, car park, and car park space. In practice that is +rarely a problem since those items are often the event’s source, and +will therefore be in the source block. + +You need RESTEvents in your licence for all the above. + +8.10 allows creating external events with the RESTCreateEvents licence. +Clients can use the usual item types as the source of the event and can +attach one of each of these as related items: + +* cardholders, +* operators, +* entry access zones, +* access groups, +* lockers and locker banks, and +* doors. + +=== Cardholders and supporting items + +7.90 allows most administrative functions on cardholders, including full +credential maintenance. + +It also gives read-only access to supporting items: + +* access groups, +* competencies, +* card types, +* roles, +* lockers, +* (in 8.10) PDF definitions, +* (in 8.50) default elevator floors, and operator privileges. + +8.20 allows moving cardholders between access zones. To support that it +added a call to the access zones controller that requires the +cardholders licence, not RESTStatus as the other access zone calls do. + +8.30 allows subscribing to cardholder changes, for integrations that use +Command Centre as a source of users. + +8.40 shows an access group’s access zones, Salto items, and privileges +(there are 20). In the Access Group window in the Configuration Client +these are the 'Access', 'Salto Access', and 'Privileges' tabs. These are +all read-only fields. + +8.50 adds views of operator groups, receptions, and visitor management +settings, and read-write access to visits and cardholders’ operator and +elevator settings. + +8.80 adds cardholder redaction: scrubbing a person's activity and identifying information out of +the database to help with privacy protection regulations. + +Car parks remain on the roadmap. + +These cardholder functions require the RESTCardholders licence. 8.20 +added lockers and locker banks to the RESTStatus licence as well, minus +the cardholder information. + +=== Non-cardholder items + +8.00 allows read access to basic configuration, status, and all +overrides, to: + +* access zones, alarm zones, and fence zones, +* doors, +* macros, and +* outputs and (in 8.10) inputs. + +You will need the RESTStatus licence for the GETs and RESTOverrides for +the override POSTs. 8.60 added the GETs to RESTOverrides, but in a +slightly limited way: they will search for items to override, but they +cannot return their status. + +8.20 added lockers, previously only visible with the RESTCardholders +licence, to the RESTStatus licence. + +8.30 added the ability to monitor more than one item per connection. + +8.50 allows read access to day categories and read-write access to +schedules. You can manage the day categories and times on a schedule, +but not which items it affects. + +Day categories are divisionless, which led to a slight change in the +`/items` controller: divisionless items did not appear there before +8.50; now they do (subject to privilege checks, of course). + +8.50 allows viewing elevator groups. + +As of 8.60, cardholders, schedules, and visits are the only items you +can modify via the API. diff --git a/training2/index.html b/training2/index.html new file mode 100644 index 0000000..9a3a484 --- /dev/null +++ b/training2/index.html @@ -0,0 +1,6403 @@ + + + + + + + +Command Centre REST API training material + + + + + + +
+
+
+
+

WORK IN PROGRESS.

+
+
+

This document is an AsciiDoc port of a document used internally at Gallagher Group to train +developers in the use of Command Centre’s REST API. This version is not yet suitable for +publication, but if you are using the REST API, and like the sound of 60-odd more pages of reading +material, and are tolerant of casual language and a choppy structure meant to help a live speaker +keep the attention of a room of developers, please continue.

+
+
+

Also, please contact Gallagher through your channel partner and talk to our Technical Support +Engineers about your project, because we are eager to help you make your integration the best it +can be.

+
+

Disclaimer

+
+

This document gives certain information about products and/or services +provided by Gallagher Group Limited or its related companies (referred +to as "Gallagher Group").

+
+
+

The information is indicative only and is subject to change without +notice meaning it may be out of date at any given time. Although every +commercially reasonable effort has been taken to ensure the quality and +accuracy of the information, Gallagher Group makes no representation as +to its accuracy or completeness and it should not be relied on as such. +To the extent permitted by law, all express or implied, or other +representations or warranties in relation to the information are +expressly excluded.

+
+
+

Neither Gallagher Group nor any of its directors, employees or other +representatives shall be responsible for any loss that you may incur, +either directly or indirectly, arising from any use or decisions based +on the information provided.

+
+
+

Except where stated otherwise, the information is subject to copyright +owned by Gallagher Group and you may not sell it without permission. +Gallagher Group is the owner of all trademarks reproduced in this +information. All trademarks which are not the property of Gallagher +Group, are acknowledged.

+
+
+

Copyright © Gallagher Group Ltd 2021. All rights reserved.

+
+
+

Gallagher Group Limited
+PO Box 3026
+Hamilton
+New Zealand
++64 (7) 838 9800
+E-Mail: sales.nz@security.gallagher.com
+Website: www.gallagher.com

+
+
+
+
+

Introduction

+
+
+

DRAFT DO NOT DISTRIBUTE.

+
+
+

This document is an introduction to using the REST API in Command +Centre, aimed at those involved in the development of software that will +integrate Command Centre into other solutions. It was written to +accompany an informal education session with a Gallagher trainer.

+
+
+

It covers features first released in 7.80 and expanded in 7.90.

+
+
+

It uses the following styles for guided examples:

+
+
+
Sample REST query and the resulting JSON
+
+
// GET /api (1)
+{
+  "a text field": "string", // including comments
+  "a numeric field": 1234,
+  "a Boolean field": false
+}
+
+
+
+
    +
  1. +

    Strictly speaking, JSON does not contain comments.

    +
  2. +
+
+
+

This style indicates filenames, URLs, and text that benefits from vertical alignment.

+
+
+
+
Fixed-width blocks are client requests that you can copy out for your own work,
+and server responses, pretty-printed a little to make them readable.
+
+
+
+

Exclusions

+
+

This document does not cover special handling of PIV cards. It shows how +to create a generic card and leaves the variations for PIV and PIV-I to +the developer documentation.

+
+
+

Nor does it cover some of the features added after v7.90: access zones, +alarm zones, fence zones, doors, outputs, inputs, PDF definitions, +and macros. Moving cardholder between access zones, operators, visitors, +schedules, and elevator groups. Subscribing to cardholder updates, an +efficient way of monitoring large numbers of items, and PII redactions.

+
+
+
+
+
+

Start here

+
+
+

Do you want to learn the bare minimum about Command Centre (Gallagher’s access control product) to +get you started on an integration? Read Useful background.

+
+
+

Do you want to set up Command Centre and try out its API from a REST client? Read +Training setup.

+
+
+

Do you want to learn something about HTTP queries in general? HTTP requests will help.

+
+
+

Do you need an introduction to how HTTPS uses certificates? +Authentication and encryption certificates is for you.

+
+
+

Do you want to use client-side certificates to authenticate your client? Very wise. +Client-side certificates has what you need.

+
+
+

Are you more concerned about the security of the API? Look in The request process for how the +server authenticates and authorises requests in general, Client-side certificates for how it +can use certificates to authenticate clients, Notes for penetration testers for a mini-FAQ +on pen test findings, and The API gateway for deep technical information on the API gateway in the cloud.

+
+
+
+
+

References

+
+
+

This document refers to API documentation on github and the online help and sample code +on the Command Centre ISO (or DVD, if you have physical media).

+
+
+

Developer API documentation

+
+

https://gallaghersecurity.github.io/ holds the reference API +documentation. That is the primary reference for the REST API, so it +aims to be complete, and you should have it on hand whenever developing +against Command Centre. However the amount of detail can be daunting and +it is not very introductory, which is why this document exists.

+
+
+

That reference documentation is in four sections:

+
+
+
    +
  • +

    cardholders.html describes the cardholder API calls and supporting concepts, such as card types, +access groups, PDFs, roles, and competencies. These functions were new to 7.90. 8.30 added a +cardholder change-tracking API;

    +
  • +
  • +

    piv.html covers the additional fields you supply and see on PIV and PIV-I cards;

    +
  • +
  • +

    events.html covers the alarms and events calls. This is all that was available in 7.80. 8.10 added +the ability to create your own events;

    +
  • +
  • +

    rest.html covers Command Centre items that are not cardholders, alarms, or events: alarm zones, +access zones, fence zones, outputs, doors, and macros arrived in 8.00, and inputs in 8.10. 8.30 +added a way to mass-monitor items. Schedules and elevator groups arrived later still.

    +
  • +
+
+
+

We are always improving the content so it is best read online, but if you need an offline copy you +can download a ZIP from https://github.com/GallagherSecurity/cc-rest-docs. The documentation is no +longer on the Command Centre install media.

+
+
+

If running on Windows, something in the mix of Internet Explorer, +Javascript, and file: URLs on network shares prevents the HTML rendering +properly so if those files look goofy to you, try a different browser or +copy the folder to your local drive. Or read it online.

+
+
+
+

API gateway technical paper

+
+

If you are thinking of using The API gateway this document will get you started, but if you are +after a deeper understanding:

+
+ +
+
+

On the DVD

+
+

The Configuration Client’s Help menu opens a CHM file that you can also +find in the ISO at +Setup\Program Files\Gallagher\Command Centre\Client\Resources\en +or Setup\Program Files\Gallagher\Command Centre\Bin\Resources\en. There +is a PDF version, split into three volumes, on the ISO in the +Documentation folder.

+
+
+

The Command Centre hardening guide, also on the ISO, is required reading +for security-conscious sites. While you may not be able to follow its +leading advice regarding the REST API ("leave it turned off") there is +plenty more in there to be aware of.

+
+
+
+

Sample code

+
+

See Utilities/REST API/REST API Sample Code.zip in the Command Centre +ISO. There is a WPF client in there and a console application in a C# +Visual Studio solution.

+
+
+
+

Abbreviations

+
+
+
AWS
+
+

Amazon’s cloud.

+
+
CC
+
+

Command Centre, Gallagher’s access control product.

+
+
PDF
+
+

Personal Data Field. Not Adobe’s kind. In Gallagher’s defence, these PDFs predate +Adobe’s.

+
+
PII
+
+

Personal Identifiable Information. Any information in Command Centre’s database concerning a +person, including names, PDFs, movement events, group memberships, roles, competencies, etc.

+
+
+
+
+
+
+
+

Training setup

+
+
+

If you wish to try the REST API for yourself, you will require:

+
+
+
    +
  • +

    Command Centre 7.90 or later with a RESTCardholders licence, a RESTEvents licence if you are to +examine events, RESTStatus if you are to look at site items, RESTOverrides if you want to override +them, and RESTCreateEvents if you wish to create events. This document does not cover the last +three.

    +
  • +
  • +

    A host capable of reaching port 8904 on Command Centre via HTTPS, or access to the desktop of the +CC server itself.

    +
  • +
  • +

    (Recommended) the sample REST client application from the Command Centre installation media (8.10 +onward).

    +
  • +
  • +

    (Optional) the Postman installer, or access to it on the internet. Any REST +client will do, but this document shows how to set up Postman.

    +
  • +
  • +

    (Optional) Chrome and access to the internet for two extensions. Chrome can be easier to use than +Postman, in some cases. Again, any web browser will do, but this document shows how to set up +Chrome.

    +
  • +
  • +

    (Optional) wget or curl, two command-line utilities commonly found on non-Windows systems.

    +
  • +
  • +

    The API developer documentation.

    +
  • +
+
+
+
+
+

Useful background

+
+
+

This section contains material you should have aboard before reading on. +Skip it if you are familiar with CC.

+
+
+

Cardholders

+
+

Cardholders are user accounts. Depending on what you give a cardholder +account it can suit different purposes:

+
+
+
    +
  • +

    people with cards and access needs, but no administrative responsibilities. The REST API allows +management of these kinds of cardholders;

    +
  • +
  • +

    administrative people (operators) with all that plus the rights to configure the system and manage its +users. The 8.50 API was the first with features for managing these kinds of cardholders;

    +
  • +
  • +

    system accounts with no person associated and no physical access, but administrative access to the +system. You are about to create one of these.

    +
  • +
+
+
+
+

Operators and operator groups

+
+

Operators are cardholders with benefits. Cardholders become operators through +membership of one or more operator groups. An operator group bestows +privileges on its members, including the ability to log in to the +Command Centre thick clients or run REST queries.

+
+
+

Operator groups have no effect on access control, so they do not appear in this document again +except when creating an operator which puts a cardholder in an +operator group while setting up a REST client. Operator groups came to the API in 8.50.

+
+
+
+

Access groups

+
+

Cardholders can be members of any number of access groups. An access +group can be a member of one other: its parent. Command Centre considers +a member of a group to be a member of all the groups up its parenting +line, as you would expect.

+
+
+

A cardholder must be a member of an access group before he or she can +open a door, so every cardholder that represents a person should have +group memberships. (Footnote: there are exceptions of course. Some +visitors, for example, do not need to open doors, but they exist in CC +so that it can record their location as they move around the site with +an escort opening doors for them.)

+
+
+

A cardholder can have many memberships of the same group. This is useful +because each has its own start and end times. Past memberships fade +away.

+
+
+

Access groups are not operator groups. When this document refers to a +group it means an access group.

+
+
+

A cardholder must be a member of an access group before he or she can +have personal data, next.

+
+
+
+

Personal data fields (PDFs)

+
+

A Personal Data Field adds a custom value to a +cardholder. Each PDF has a type (text, image, numeric, date, telephone +number, email address, …) and optional constraints on the values that it +can hold. For example, text, email, and telephone number types can have +a regular expression attached which a new value must match before +Command Centre will accept it. A date can have a maximum and a minimum. +Text PDFs can have a list of valid values, like an enumeration.

+
+
+

There is more configuration: image PDFs have a type and size, to which +Command Centre will transcode incoming images. Mobile numbers and email +addresses have a flag indicating whether they are suitable to receive +SMS and email notifications. 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 (Footnote: direct or inherited. Unless otherwise noted, all +Command Centre’s access group membership tests treat inherited members +just like direct members).

+
+
+

The REST API allows you to manage a cardholder’s group memberships (so +that he or she has the PDF) as well as see and set PDF values. It does +not let you change the configuration of the PDF itself.

+
+
+

Whenever this file or the API’s reference documentation uses the term 'PDF' it means a personal data +field.

+
+
+
+

Divisions

+
+

Every item in the API—​we will get to items in a moment—​is in a division (footnote: except day +categories. They are divisionless). Divisions are arranged in a tree: each has exactly one parent, +aside from the root division, which has none. An operator group specifies the roots of the division +trees to which it grants privileges.

+
+
+

Therefore an operator with privileges on the root division has those +privileges on all that server’s objects.

+
+
+

Complication: multi-server clusters have one root node (and therefore +one tree of divisions) per server.

+
+
+

If you find that an operator cannot see or modify an item, the questions +you should ask are:

+
+
+

Which division is the item in?

+
+

The Command Centre client shows a cardholder’s division in the +'Cardholder Details' pane of the cardholder viewer. The Configuration +Client shows the division of any item in the 'General' tab of its +property page. The REST API shows it in the 'division' field.

+
+
+ + + + + +
+
Important
+
+The operator’s division and his or her operator groups' divisions in the 'General' tabs +are irrelevant to privileges. The operator group grants privileges on the divisions in the +'Divisions' tab. +
+
+
+

There is a small section on operator privileges below.

+
+
+
+
+

Access zones

+
+

An access zone represents a physical area with Gallagher-controlled doors +on its perimeter. Something like a room.

+
+
+

An access zone can be open or secure. If open, all its doors are unlocked, +but when it is secure the doors are locked and cardholders attempting entry +will be subject to an access check.

+
+
+

Since a real door has a space on each side of it a Command Centre door can +have two zones attached: an entry zone and an exit zone. The only +difference between the two is the event that Command Centre creates when +someone badges from one to the other: 'access granted' or 'exit granted'.

+
+
+

No matter which way around the zones are attached to a door, when a +cardholder moves through it the resulting event calls the the zone into +which they moved the 'entry' zone, and the one they just left the 'exit' +zone.

+
+
+
+

Roles

+
+

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. It makes more sense when you use the example +'supervisor': a person has a supervisor and is a supervisor for many +others. 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 you can change the cardholder’s supervisor, but to change +who the cardholder supervises you need to edit those individuals.

+
+
+
+

Competencies

+
+

Basically, competencies are another condition that a cardholder must +meet to pass an access check at a door.

+
+
+

The REST API lets you manage the links between cardholders and +competencies: create them, delete them, enable/disable them, and set +their expiry dates.

+
+
+

You might like to sit down for this part. +A competency can be disabled, expired, both, or neither. Actions at a +door can depend on whether a competency is disabled, expired, soon to +expire, or all good.

+
+
+

Whether it is enabled is a flag, plain and simple. Whether it is +expired is derived from an expiry timestamp: if it is +in the past, Command Centre considers the competency expired.

+
+
+

A competency can also have an enable date. If that date (timestamp) +passes while the competency is disabled, Command Centre will enable it.

+
+
+

If the competency is not disabled, the 'expires' time is important. If +it is in the past, the cardholder’s competency is expired. If it is not +set, or it is in the future, the cardholder benefits from the +competency.

+
+
+

A cardholder can have only one link to each competency. They differ from access groups in that way.

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Enabled flagEnablement dateExpiry dateStatus

Set

-

Far future

Active

Set

-

Near future

Active (with a warning at the door)

Set

-

Past

Inactive (expired)

Unset

Future

-

Inactive (pending)

Unset

Past

-

Inactive (disabled)

Unset

null

-

Inactive (disabled)

+
+
+

Card types

+
+

A card type carries rules for the data that a card carries, PINs, how to +treat cards around their expiry time, and default values for new cards +of that type. We often use the word "credential" because not all card +types involve a physical card: there are also biometric and mobile card +types.

+
+
+

The REST API provides read access to card types so that you can manage +cardholders’ credentials.

+
+
+

PIV cards have their own developer document, separate from the rest of +the cardholder API.

+
+
+
+

All the above are items

+
+

The API lets you search for items and examine them, but—​other than +cardholders and schedules—​it does not let create, alter, or delete them. +The purpose of the cardholder API is to let you associate items with +cardholders and manage those associations.

+
+
+
+ +
+

Talking about a PDF or a competency can be confusing, because there is a +PDF item and a competency item, and cardholders can have PDFs and +competencies, but the item and the cardholder’s link to the item are +different things.

+
+
+

The items (on the left in the table below) and the connection to a +cardholder (on the right) both appear in the REST API, but the API only +lets you change the things in the right column, the connections. +So let +us make some definitions:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ItemA cardholder’s possession of that item

Access group

Group membership

Competency

Cardholder competency

Role

Relationship
+(the role is the nature of the relationship between two cardholders)

Locker

Locker assignment

PDF

Cardholder PDF
+or
+PDF value

+
+

There is a question of scale. You may have only two competencies, but thousands of cardholders with +those competencies. You may have only one role in the system, but every one of your cardholders +might have a relationship using that role.

+
+
+

For that reason, the API calls that list items such as roles and competencies do not list their +connections to cardholders. The result sets would be too large. Instead, +you see those connections from the cardholder side: when you GET a +cardholder’s details, you will see all the connections that cardholder +has to PDFs, competencies, groups, cards, lockers, and roles.

+
+
+

Access groups and operator groups will show you their cardholder +members, but only if you ask.

+
+
+
+

Events and alarms

+
+

Events record occurrences in the system. They are not items. They have +an ID, a source item, an occurrence time, and links to other related +items. Events are immutable: the events you read from the REST API will +not change.

+
+
+

Alarms are events with extra fields, and some of them are mutable: there +is a free-text notes field that you can edit in the thick clients, a +history, and Booleans recording whether the alarm is acknowledged, +processed, and active.

+
+
+

The alarms interface only shows unprocessed alarms in its search +results. Once an operator processes an alarm, it disappears from alarm +searches. However, the alarm still exists in the database and an alarm +is also an event, so the events interface will return it whether +somebody processed it or not.

+
+
+
+

Operator privileges

+
+

Or just 'privileges' since there is no other kind.

+
+
+

An operator has privileges over a division and all its subdivisions. +When we refer to an operator having a privilege on a cardholder, for +example, we mean that the operator has that privilege on the +cardholder’s division, or one of its ancestor divisions.

+
+
+

In the interests of security, you should give your REST operators +(footnote: all operators) the minimum privileges they require to +achieve their task.

+
+
+

Appendix: Features and licences gives examples of privileges you will need for various tasks.

+
+
+
+

HTTP requests

+
+

An HTTP request has four parts: a verb, an address, a handful of +headers, and a body.

+
+
+

Verbs

+
+

The verbs we will use are GET, POST, PATCH, and DELETE (in upper case by +convention). GET and DELETE are self-explanatory but the other two are +often confused. In this API we use POST to create something new such as +a cardholder, and PATCH to modify something like the end-date on a group +membership.

+
+
+
+

Addresses (URLs)

+
+

The address is the URL that everyone is accustomed to. In a REST API the +address identifies the object you wish to GET, PATCH, or DELETE. When +POSTing, the address identifies the type of thing you wish to create.

+
+
+
+

Headers

+
+

Headers are a list of key/value pairs. We use one called Authorization +(spelled with a Z) to carry client authentication, and one called +Content-Type to be clear that we use JSON.

+
+
+
+

Bodies

+
+

The body of a GET or DELETE request is empty. A POST can also be empty, +but they usually carry some instructions for what you want created. A +PATCH always needs a body that contains instructions for how to modify +the item identified by the address.

+
+
+

If the body is not empty, it must contain JSON (below).

+
+
+

The sample application "CCFT REST Client" opens a console window that +shows you the verb and address of the HTTP queries it is making. It can +also show you the bodies of its queries and the server’s responses.

+
+
+
+
+

HTTP responses: codes, and more headers and bodies.

+
+

An HTTP response has three parts: a numeric response code, more headers, +and a body.

+
+
+

Response codes

+
+

Any response in the 200-299 range means success. GETs return a 200 along +with their results. DELETEs and MODIFYs return 204 ("no content"), +which just means they succeeded and having nothing more to say. Creating +a cardholder or event returns 201 ("created").

+
+
+

A response in the 400-499 range generally means there was something +wrong with the request. One exception is 409: it could mean that your +timing was bad and trying again later may succeed. Along with 403 and 404, 409 +could also mean you have attempted to do something beyond your +privilege. 401 means you did not sent a good Authorization header and +the server does not trust you at all. +The body of the response that comes back from the server will +tell you the problem.

+
+
+

500-level responses mean the server has met with trouble. Waiting for +updates on events or items is an exception: if you ask Command Centre +for updates and none arrives before the timeout, it will return 503. +That is actually a kind of success: it means nothing changed while you +were waiting, so a future version of Command Centre may return a +200-level code in this case.

+
+
+
+

Headers

+
+

The only time Command Centre returns a header of interest is after it +processes a POST to create a cardholder or event. It sets a header +called Location containing the URL of your new object.

+
+
+
+

Bodies

+
+

The body of a GET response contains everything you asked for, in JSON. +The body that comes back from other verbs is empty unless there was a +problem.

+
+
+

Chrome will show you the body. Press F12 and resend the request to make +Chrome show you the response code and the headers as well (along with +lots of other useful information). Postman (a web client we will get to +later) always shows you everything.

+
+
+
+
+

JSON

+
+

…​though familiarity with XML or any programming language should be +enough. With line breaks and indentation and a bit of colour, JSON is +quite readable.

+
+
+

JSON can contain flat fields, objects (structures), and arrays.

+
+
+
+
{
+  "a text field": "string",
+  "a numeric field": 1234,
+  "a Boolean field": false,
+  "an object": {
+    "sub-field1": "foo",
+    "sub-field2": "bar"
+  },
+  "an array": [
+    {
+      "sub-field1": "jingle",
+      "sub-field2": "bells"
+    },
+    {
+      "sub-field1": "foo",
+      "sub-field2": "bar"
+    }
+  ]
+}
+
+
+
+

In that example, the array called an array shows an array containing two +more objects, each of which contains two fields of its own.

+
+
+
+

Authentication and encryption certificates

+
+

Before an API call can succeed the client needs to decide to trust the +server and then the server needs to decide to trust the client. They do +that using certificates.

+
+
+

First a little background. Very simply put, the current algorithms for +secure communication require a pair of keys. Keys are nothing more +than huge numbers. The two in the pair are different from each other, +but mathematically related so that when you encrypt some data using one +key, nobody can decrypt it if they do not have the other key. The key used for +encryption is public because there is no harm in encrypting data. +People toss those keys around like business cards. Its mate, however, +is very, very private, because it is the one that unlocks the secret message.

+
+
+

These keys do more than just encrypt +and decrypt data so we do not use those terms in their names. The business +card one is called the public key and the other is +the private key.

+
+
+

A certificate contains a public key plus metadata: what the key is +meant for, how long it is good for, and some proof that it is authentic, +if there is any. That proof of authenticity takes the form of a +signature from an authority that the internet has agreed to trust, such +as Symantec or Verizon. Client certificates, and some server +certificates, do not have a signature. Or they do, but it is their own +signature, which does not really count because nobody trusts it. Such +certificates are called self-signed. Web browsers cook up their own +self-signed certificates all the time. Web server certificates, on the +other hand, last for months or years.

+
+
+

If an HTTPS client and server connect and establish an encrypted channel +of communication without checking certificates, it will be secret (nobody +will be able to listen in) +but they should not trust each other. The other end could be +fibbing. So they conduct a negotiation to establish each other’s +identity (i.e., they authenticate).

+
+
+

Usually a web client requires proof of authenticity from the server, +since you want to be sure that it really is your bank’s web site you are +looking at and not a fake. If the server does not provide that, the +client shows a warning. When working with the Command Centre API you +will have to work around it in +Chrome and +work around +it in Postman. If you want to install your own server key, the topic +'Changing the Web Services' in the Configuration Client’s online help +shows you how.

+
+
+

Sometimes the server also requires a proof of identity from the client. +This does not happen when using most web sites because (continuing the +bank example) your bank does not care where you are coming from. +It uses your password to authenticate you. But APIs should operate more +securely than web sites so our recommendation is to turn on the feature +that makes the server check your clients' certificates. +Client-side certificates covers client +certificates.

+
+
+ + + + + +
+
Important
+
+Know the difference between the two certificate checks, and that they are completely +independent. If the client drops a connection because it does not trust the server, Command Centre +cannot raise any alarms, because it never received a proper connection. The problem is on the client +and there is nothing you can do to Command Centre to help. But if the server certificate is +acceptable to the client, the server has a chance to check the client certificate. If the server +does not like the client certificate, Command Centre will raise an 'invalid client certificate' +alarm. +
+
+
+
+
+
+

Enable Command Centre’s API

+
+
+

Turn on the web server

+
+

Configuration client → File → Server Properties → Web Services (about 15 down).

+
+
+

Enable the REST API and–-for the moment–-tick the checkbox to the right of the port. This is an old +screenshot: it changed labels in 8.50. Have a good look at the status because if your server has a +problem binding a socket (which just means "listening"), it will show here first.

+
+
+
+Enabling the public API +
+
Figure 1. Enabling the web server
+
+
+

The Configuration Client’s online help covers this in the topic called +'Web Services'.

+
+
+ + + + + +
+
Warning
+
+⚠ Make sure 'Do not require pinned client certificates' is off in production. In 8.50 it +changed its name to 'Enable REST Clients with no client certificate'. It ships turned off: make sure +it stays off on production servers. +
+
+
+

Requiring pre-shared certificates from clients is the best protection +the server has against attackers on its network. If you tick the box to +turn off that check when you first start your development, come back +once your application is connecting successfully and untick it again. +Read Client-side certificates to help get your application connecting again +after doing that.

+
+
+
+

Installing a custom server certificate

+
+

You do not need to install a custom server certificate for experimental development. If you +eventually choose to do it, it all happens under a button that arrived in Command Centre after I +took the screenshot above, labelled 'Manage Certificates'. The Configuration Client’s online help +covers it in detail in a section called 'Replacing the web service certificate' in the 'Changing the +Web Services' topic. You can either import a public/private key pair into Command Centre (which is +simple, secure, and recommended) or use the Windows Certificate Store (which uses Microsoft’s +security instead of Command Centre’s). The summary of the Certificate Store process is: you need to +name your certificate 'Gallagher Command Centre Server' (please take care with the spelling), place +it in the 'Gallagher Applications / Certificates' folder of the Local Computer Certificate Store, +and give Command Centre the rights to use it. The online help lays that out step by step.

+
+
+
+

Create a REST operator

+
+

We will get to the reasons why in Why we need an operator.

+
+
+

Create an operator group and give it the necessary privileges

+
+

You can do this in either the operational client ("Command Centre") or the configuration client.

+
+
+

Give the group the lowest level privileges it needs. For this exercise, +you will need 'Create and Edit Cardholders' and 'Edit Alarms'. 'Modify +Access Control' and 'View Site' could be handy later.

+
+
+
+Adding privs to an operator group +
+
Figure 2. Adding privs to an op group
+
+
+ + + + + +
+
Warning
+
+Not 'Advanced User'. Never 'Advanced User'. +
+
+
+

See this appendix for a table of +privileges an operator needs for common tasks.

+
+
+

One group is enough for experimenting but when it comes to production, +create an operator group for each class of client you have connecting +and give each group different privileges. An operator can be in more +than one operator group; use this flexibility as you need.

+
+
+
+

Create a cardholder and add it to the operator group

+
+

You can do this in either of the clients.

+
+
+
+Adding cardholders to an op group +
+
Figure 3. Adding cardholders to an op group
+
+
+

In production, your operator should have a bare minimum of +capabilities, so do not give it a card, logon, password, or user code. +Do give it plenty of description about what it does, where it connects +from, and who to contact about it, because the people running the +security system will not be the people who run your software +integrations and they will need all the help you can give them when +problems arise.

+
+
+

During development it helps to log in to the Command Centre clients +sometimes, so I give the REST operator a logon, password, and the +'Launch Configuration Client' privilege.

+
+
+

Now that you have an operator, you need to let the REST API use it.

+
+
+
+
+

Create a REST Client item

+
+

…(in the server) and assign an operator.

+
+
+

We call it a 'REST Client' but it is really a mapping from an API key +to an operator. More on this later.

+
+
+

Using the Configuration client, Configure → Services and Workstations +(at the bottom). Right-click menu → New → REST Client.

+
+
+

Set a name, then go to the 'API Key' tab.

+
+
+

Drag your new operator (Manage → Cardholders) into the 'REST Client +Operator' box. That box looks like it can hold more than one: it cannot.

+
+
+

Take a note of the API key. You will need it for your clients (the +sample app, Chrome, or Postman).

+
+
+
+A REST Client item’s API key in the Configuration Client +
+
Figure 4. A REST Client item’s API key in the Configuration Client
+
+
+

IP filtering is a layer of security that makes it that much harder for +an attacker to attack your server.

+
+
+
+A REST Client’s IP filters in the Configuration Client +
+
Figure 5. A REST Client’s IP filters in the Configuration Client
+
+
+

(A space is as good as a comma.)

+
+
+
+

What is an API key?

+
+

Your client sends this to Command Centre with every request. It is the +username and password combined. Take care of it. If someone steals your +API key and you have not taken other precautions (client certificates +and IP filtering) they could masquerade as you.

+
+
+

If something makes an API call without an API key, or with an API key +that Command Centre cannot find on one of the REST Client items, CC will +raise an error 'A REST connection was attempted with an invalid API +key'.

+
+
+

When a client sends it to the server in an HTTP header it prepends +GGL-API-KEY and a space. That string is not part of the key and you +should not use it in any of the places that expect an API key. It is +just there so the HTTP request conforms to an Internet standard.

+
+
+
+
+
+

Try the sample client application

+
+
+

This section needs fleshing out with proper prose, but until that +happens the major points to cover are:

+
+
+

The sample client is the quickest way to make sure CC is working +properly. For Windows users, it is better than a web browser (which +requires plugins and hides error messages) or Postman (which is fiddly +if the server is checking client certificates).

+
+
+

Find the sample client on the installation media under Utilities / REST +API / RESTClient_version. It has been there since 8.00.

+
+
+

It is not a Command Centre management application! It is a library of +sample code for developers, which happens to compile and run. You can +check the status of many items, override most of them, watch and create +events, and create, look up, and move cardholders, but some features +like lockers and car parks are missing.

+
+
+

Watch the multi-coloured console to find out what URLs to use in your +own requests. Later versions include options on the login screen to also +show the JSON that the client is sending and receiving.

+
+
+

The source code for the demo app and a few others is on the installation +media.

+
+
+
+
+

Set up Chrome

+
+
+

If the sample GUI app works and you want to see the data that comes from +Command Centre, a web browser is all you need. If you also want to +create and change items you should skip this section and install +Postman.

+
+
+

There are two extensions you need to install for Chrome to be really +useful. One sends the API key to the server, and the other dresses up +the JSON that it sends back.

+
+
+ + + + + +
+
Note
+
+Since writing this, several browser extensions have appeared that test REST APIs quite +thoroughly. They may provide a better experience. Shop around. +
+
+
+

Install the ModHeader extension

+
+

You need to set a custom header, because that is how we send the API key +and without that Command Centre will give you nothing.

+
+
+

Start by clicking the 'Modify Headers' icon in Chrome. (Footnote: +confusingly, there is also an extension called 'Modify Headers', which +is different from 'ModHeader'. Use either.)

+
+
+

Set a header called Authorization with a value of GGL-API-KEY followed +by a space and the API key you took from the configuration client. Note +in the example below I have two headers ready to go, only one of which +is active. They are too wide for the Modify Headers window (there are +three more characters).

+
+
+

In 7.90, both must be in upper case.

+
+
+ + + + + +
+
Warning
+
+⚠ Set a filter so that the header only goes to your Command Centre +server. Otherwise Facebook will have your API key. +
+
+
+

Use a URL pattern in the filter that all your queries will match but +other web browsing will not. ModHeader now uses regular expressions, so +if you have dots in your hostname you must put backslashes in front, +\..

+
+
+
+Configure Mod Header Chrome extension +
+
Figure 6. Configure Mod Header Chrome extension
+
+
+
+

Install a JSON viewer

+
+

Raw JSON straight from the server contains no whitespace, so it is not +that easy to read. There are a few Chrome extensions that pretty-print +JSON for you. I use 'Awesome JSON Viewer' because it is recent (April +2020) and can collapse and count sub-items. It is rebranding itself +'JSON Viewer Pro', so you might try searching for that. Despite having +'pro' in the name it remains free.

+
+
+
+

Ignore server certificate warnings

+
+

Send Chrome to https://yourhost:yourport/. yourport will be 8904 unless you changed it when +you set up the web server. If your server does not have a certificate +with a trust path to a trusted root certificate, you need to click through the warning below. It +will reappear occasionally. You can turn it off in Chrome but it is not a good idea, since you want +to know when other servers are using self-signed certificates.

+
+
+
+Chrome fretting about a server cert +
+
Figure 7. Chrome fretting about a server cert
+
+
+
+Chrome fretting in more detail +
+
Figure 8. Chrome fretting in more detail
+
+
+
+
+
+

Set up Postman

+
+
+

If you want to do more than look, you need Postman, because Chrome does +not let you POST, PATCH, or DELETE as easily as Postman does.

+
+
+

Postman used to be a Chrome extension but is now a standalone +application. Both work. These screenshots are from the application.

+
+
+

Send the API header with every request

+
+

This is what the Modify Headers extension does in Chrome. It makes +Postman send an Authorization header containing your API key with every +request.

+
+
+

Your requests also need a Content-Type header but you do not need to set +it yourself. Postman will add that after the next step.

+
+
+
+Setting auth header in Postman +
+
Figure 9. Setting auth header in Postman
+
+
+

There is a mistake in that screenshot: the value for the Authorization +header should have GGL-API-KEY and a space before the API key. Later versions of Command Centre +will not work without it.

+
+
+
+

Set the content type to JSON

+
+

Otherwise Command Centre will reject it as invalid.

+
+
+
+Content-type Postman header +
+
Figure 10. Content-type Postman header
+
+
+

You must use application/json, in lower case, nothing more. People +have tried adding a semicolon and charset=utf8, but that just stops +all queries from working.

+
+
+
+

Never mind that your server certificate is self-signed

+
+

In the current version of Postman, the settings are behind the cog in +the top tool bar, not the sliders in the environment toolbar below it.

+
+
+

For older versions of Postman, the settings are behind the open-ended +wrench in the top tool bar, not the cog in the environment toolbar below +it.

+
+
+
+Postman settings menu +
+
Figure 11. Postman settings menu
+
+
+

Pick 'Settings' and turn off SSL certificate verification. Turn off the +other options if you want to keep it looking clean. It makes no +difference to Command Centre.

+
+
+
+Postman SSL cert verification off +
+
Figure 12. Postman SSL cert verification off
+
+
+
+
+
+

First GETs: cardholders

+
+
+

The most basic GET

+
+

Using Chrome, go to https://yourserver:8904/api again. This document +and the developer documentation use the following shorthand for that instruction, which omits +the protocol, host, and port:

+
+
+
+
GET /api
+
+
+
+

Doing that will test everything you have set up so far. If it did not +work, look at the error message in the response body (Chrome will show +it) and the most recent events in Command Centre.

+
+
+

If it did accept your API key, the only thing that can stop you now is a +licensing problem:

+
+
+
+
{
+  "message": "feature not licensed"
+}
+
+
+
+

With a RESTEvents licence you will get more:

+
+
+
+
{
+  "version": "7.90.0.0",
+  "features": {
+    "items": {...},
+    "alarms": {...},
+    "events": {...}
+  }
+}
+
+
+
+

With a RESTCardholdersEvents licence:

+
+
+
+
{
+  "version": "7.90.0.0",
+  "features": {
+    "items": {...},
+    "alarms": {...},
+    "cardholders": {...},
+    "events": {...},
+    "accessGroups": {...},
+    "roles": {...},
+    "lockerBanks": {...},
+    "competencies": {...},
+    "cardTypes": {...}
+  }
+}
+
+
+
+

That is not the exact JSON you will get (that is not even JSON) but hopefully +you get the idea.

+
+
+
+

Cardholder summary

+
+
+
GET /api/cardholders
+
+
+
+

That means you should Chrome or Postman to https://yourhost:yourport/api/cardholders.

+
+
+

Your operator should be there. Try following some of the links. If you +are using Chrome, just click on them.

+
+
+

One of the links called href (probably the first one) will take you to a cardholder’s details +page, covered in the next section.

+
+
+

Next try:

+
+
+
+
GET /api/cardholders?top=1
+
+
+
+

That limits the results to one cardholder. If you don’t have a next +link in the result, it will be because there is only one cardholder in +your system or your operator only has access to one.

+
+
+

Now apply the advice from the efficiency section of the developer +documentation for collecting a lot of cardholders at once:

+
+
+
+
GET /api/cardholders?sort=id&top=10000
+
+
+
+

v8.00 delivered the ability to add all the fields from the details page +to the summary page, using the fields parameter. See the developer +documentation for a proper description, but in short, try adding +fields=fieldname to your request URL (after a ? or & of course) +where fieldname is the name of a field you can see in a details page, +such as cards or accessGroups. For example:

+
+
+
+
GET /api/cardholders?sort=id&top=10000&fields=firstName,lastName,cards
+
+
+
+
+

Hrefs are URLs as well as identifiers

+
+

The fields called href in the cardholder summary are URLs, +and hopefully you have followed one already. Some will return you a page +of data, and some will 404. We call them hrefs rather than URLs +because they are HTML references that, in our case, happen to be HTTPS +URLs.

+
+
+

Hrefs are very important. Each object in Command Centre—​events, +alarms, items, connections between them—​has one that identifies it. +You will be sending many of them in the bodies of your requests.

+
+
+
+

Cardholder detail

+
+

Follow one of the href links on the summary page:

+
+
+
+
GET /api/cardholders/1234
+
+
+
+

1234 will be a different number on your system. The API documentation +uses the syntax /api/cardholders/{id}. Ignore the braces! There are no +braces in our URLs.

+
+
+

That GET shows you everything the REST API can tell you about the +cardholder (footnote: not quite everything. Mobile credentials and PIV +and PIV-I cards have blobs of data that do not come out unless you ask +for them, because they are so large). The developer documentation helps +interpret it.

+
+
+ + + + + +
+
Tip
+
+
+

The differences between a summary page and a detail page are:

+
+
+
    +
  • +

    A summary page is a search. You pass it search filters, an item limit, and sorting and pagination +instructions and it will return any number of items. A detail page will only tell you about one.

    +
  • +
  • +

    A detail page will return more fields than a summary page, by default. However you should tell +both to only return the fields you need.

    +
  • +
+
+
+
+
+

The API documentation makes heavy use of the terms summary and +detail. You see the summary of an item at root URLs such as +/api/cardholders and /api/access_groups, returned in an array of +many items of the same type as the results of a search. You see the +detail of a lone item by following the item’s href.

+
+
+

It worth becoming familiar with the structure of a cardholder in JSON +because the REST API uses it for summary and detail pages, and it +expects very nearly the same structure when you create or modify a +cardholder. They vary in the quantity of fields and their levels in the +document.

+
+
+
+
+
+

First GETs part two: events

+
+
+

List all events

+
+

This returns 1000, starting with the first recorded:

+
+
+
+
GET /api/events
+
+
+
+

If it takes a while, it is because the JSON viewer extension in Chrome +is pretty-printing it.

+
+
+

From there you can following the next link to get another thousand. +When you have extracted all the events out of Command Centre, an +updates link will replace next. The updates URL is a long poll +link: GETting it will block until more events arrive, or the call times +out.

+
+
+

If you are writing a program that will extract all events out of Command +Centre you should set top (described in the API documentation) as high +as you can. Command Centre will cap it at 10,000. You do not gain much +performance after a couple of thousand, but taking it higher reduces the +number of requests.

+
+
+
+
GET /api/events?top=5000
+
+
+
+

It might take a while for the JSON viewer to render all that JSON.

+
+
+
+

List all alarms

+
+

The alarms interface only returns alarms that have not been processed, +i.e., those that are 'current'. After an operator processes an alarm, it +is merely an event with extra fields.

+
+
+
+
GET /api/alarms
+
+
+
+

That will return at most 100 alarms. You can follow the next link to +get more, until you have got them all and an updates link replaces it. +The updates URL is a long poll: GETting it will block until more +alarms occur or the call times out.

+
+
+
+
+
+

Back into the theory

+
+
+

This section is a grab-bag of facts you need before going further.

+
+
+

API controllers

+
+

Controllers are different parts of the REST API. Not to be confused +with the controller hardware Gallagher also produces, API controllers +have the same name as the part of the request URL after the leading +/api. The main ones are alarms, events, and cardholders. items +is there to support searching for events. card_types, competencies, +access_groups, roles, operator_groups, and locker_banks let you +find items to attach to cardholders.

+
+
+

All controllers’ names are plural, and pothole_cased. You can find links +to them all with

+
+
+
+
GET /api
+
+
+
+
+

Why we need an operator

+
+

Everything that happens to a cardholder happens because an operator did +it. The operator could be a person working in one of the thick clients, +or it could be one of the other APIs, but whenever a cardholder changes, +Command Centre must have an operator to pin it on.

+
+
+

Having an operator allows Command Centre to enforce privileges. You +limit what your REST operator can do in case the client has bugs (and +starts DELETEing URLs instead of GETting them) or the API key becomes +known to the other side.

+
+
+

It also helps auditing. Each cardholder change causes an operator event, +with the operator and cardholders as related items. Interactive changes +use the workstation as the source, and REST changes use the REST Client. +You can run reports that filter on the source and operator to monitor +your integration.

+
+
+
+

The request process

+
+

This is shown in a rough flowchart.

+
+
+

All HTTPS requests start like this:

+
+
+
    +
  1. +

    The client and server establish an encrypted channel. Part of that is +a certificate exchange. The channel makes the following conversation +safe from eavesdroppers but does not confirm the identity of either +side.

    +
  2. +
  3. +

    Unless you have configured your client not to, it verifies the +identity of the server (authenticates it) by examining the contents of +the certificate that came from the server during the previous step. If +the client does not like the certificate that came from the server, it +drops the connection. Command Centre will complain to its log file when +this happens but because it did not receive a request, will not create +an event.

    +
  4. +
  5. +

    If the client trusts the server it sends its request along with a +secret that proves it is who it says it is. In our case that is an HTTP +header containing the API key.

    +
  6. +
+
+
+

So far that has been a normal HTTPS conversation, the same as what +happens with every web site you visit in a browser. From here on is +specific to Command Centre.

+
+
+
    +
  1. +

    The server looks for the API key in the Authorization header and +finds the matching REST Client (footnote: capitalised to mean the +configuration item in Command Centre, not the REST client software on +the other end of the TCP connection) in the database. If it cannot find +one, it will raise an alarm "A REST connection was attempted with an +invalid API key".

    +
  2. +
  3. +

    If you did not disable pinned client certificates in the server +properties (Web Services tab), or if you are running 8.50 and the REST +Client item has a thumbprint on it (in the API Key tab), it checks the +thumbprint of the request’s certificate against the one on the REST +Client item. If they do not match, it responds with a 401 and raises an +alarm "A REST connection was attempted with an invalid client +certificate". The server does not check the client certificate’s chain +of trust. Client-side certificates has all the +details of why you would want your server to check client certificates +and how to create them.

    +
  4. +
  5. +

    It checks the source host’s IP number against the REST Client item’s +IP filters. If it does not match, it responds with a 401 and raises an +alarm "A REST connection was refused because of the connecting IP +address does not match the IP filter on the REST Client name of your +REST Client".

    +
  6. +
  7. +

    It checks that it has a license for the controller that will handle +the request. If it does not, it sends a 403 response containing the +string "Feature not licensed".

    +
  8. +
  9. +

    It creates a new session for the operator, if there isn’t one ready, +then compares what the request is asking for against the REST Client’s +operator’s privileges from the session. If the privileges do not allow +the operation that the client requested, the server will respond with a +400-level error and a message in the body.

    +
  10. +
+
+
+

If all those steps succeed, the API controller processes the request, +logs an operator event if something changed, and returns a result.

+
+
+

The alarms above have a default priority of medium-high. The server +raises them for two reasons: while developing, it is useful to have a +little more diagnosis coming out of the server, and in production, it is +good to know when your API is being probed.

+
+
+

If too many bad requests arrive too quickly, the server will assume it +is under attack and will log an alarm at maximum priority, then will +remain silent on the matter until the attack stops.

+
+
+

Errors also go to +%PROGRAMDATA%\Gallagher\Command Centre\Command_centre.log.

+
+
+
+
+
+

First POST and search

+
+
+

Create a cardholder

+
+

In Postman:

+
+
+
+POST to create a cardholder +
+
Figure 13. POST to create a cardholder
+
+
+

Notice that there are two headers set: Authorization contains the API +key and content-type tells the server that the body is JSON.

+
+
+

This document uses this shorthand to represent that kind of HTTP query:

+
+
+
+
// POST /api/cardholders
+{
+  "firstName": "New",
+  "lastName": "Cardholder",
+  "division": {
+    "href":"https://localhost:8904/api/divisions/2"
+  }
+}
+
+
+
+

The first line gives the verb and the file part of URL. It needs the protocol, host, and port added, +to form the final request: a POST to https://yourserver:8904/api/cardholders. The rest is the +body.

+
+
+

When you create a cardholder you must specify the division and either +the first or last name, so this example is about the shortest you can +get away with.

+
+
+

Look at the response from the POST. It contains a Location header +giving the URL of our new cardholder.

+
+
+
+Create cardholder result +
+
Figure 14. The result of creating a cardholder
+
+
+

You could GET that URL to see what you created, or…

+
+
+
+

Search for a cardholder

+
+
+
GET /api/cardholders?name=new
+
+
+
+

That will return all the cardholders with 'new' in their name. It is +case-insensitive.

+
+
+

To be more precise:

+
+
+
+
GET /api/cardholders?name="cardholder, new"
+
+
+
+

Quotes make it a full string match, rather than a substring match. It is +still case-insensitive.

+
+
+

Note how Command Centre matches your search string against a +concatenation of the cardholder’s last name, a comma, a space, and the +first name. It only does that if the cardholder has both names set. +Otherwise it just uses the one.

+
+
+

Also note that Chrome will turn the space into %20.

+
+
+

You should see your new cardholder in the results of both those queries.

+
+
+
+
+
+

Cardholder flat fields

+
+
+

"Flat fields" isn’t a term the REST API uses but it means the simple data like names, description, +and PDF values that sit at the top level of a cardholder and do not have structures of their +own. Other data such as cards, access group memberships, and competency assignments are one level +down, in arrays, and contain other fields.

+
+
+

Setup: give a cardholder access groups and PDFs

+
+

For a cardholder to have a PDF, both need to be on the same access +group. You cannot create PDFs or assign them to access groups via REST +so you must do that in the Configuration Client. Adding cardholders to +groups is possible via REST of course, but that is easier if you have an +existing group membership to compare your efforts against, so for now we +will do that in the client as well.

+
+
+

Create some PDFs

+
+

In the Configuration Client, Configure → Personal Data Fields (second +from the top).

+
+
+

Add → New Personal Data Field. Call it 'email' and set the type (on the +Type tab) to Email.

+
+
+

You might as well make a few more with different data types. Make at +least one text, because they have no constraints and are easiest to +experiment with.

+
+
+
+Create a PDF in Configuration Client +
+
Figure 15. Create a PDF in Configuration Client
+
+
+
+

Create at least two access groups, add the PDFs, and add your cardholder

+
+

In the configuration client, Manage → Access Groups, right-click menu, +New → Access Group.

+
+
+

Open the cardholder and PDF lists out of the Manage menu so that you can +drag items out of them.

+
+
+

Drag your new cardholder to the Cardholder Membership tab of the access +group.

+
+
+

Drag your PDFs to the Personal Data tab of the access group.

+
+
+

Repeat!

+
+
+
+Add a PDF to a group in Config Client +
+
Figure 16. Add a PDF to a group in Config Client
+
+
+

Save everything in the configuration client then reload your cardholder’s details in your REST +client to see what PDF values and group memberships look like in JSON. These sections in the +cardholder API documentation cover it:

+
+
+
    +
  • +

    'Cardholder detail' gives the layout of a cardholder’s detail page.

    +
  • +
  • +

    'Cardholder PDF' describes the items in the personalDataDefinitions array.

    +
  • +
  • +

    'Cardholder access group' describes the items in the accessGroups array.

    +
  • +
+
+
+

Now you can change some of those values.

+
+
+
+
+

Change a name, authorise, change simple PDFs, set user code, etc.

+
+

This example changes a cardholder’s first name and two PDFs, authorises +it (de-authorised cardholders always fail access checks), turns on a +flag that allows extra unlock time on doors, and sets the user code +(which is a number you can use at keypads):

+
+
+
+
// PATCH /api/cardholders/{id}
+{
+  "firstname": "Jeremiah",
+  "@datePDF": "2099-03-31",
+  "@email": "a@b.com",
+  "authorised": true,
+  "useExtendedAccessTime": true,
+  "userCode": "1234"
+}
+
+
+
+

It looks like this in Postman:

+
+
+
+PATCH a cardholder in Postman +
+
Figure 17. PATCH a cardholder in Postman
+
+
+

It looks a lot like that in the cardholder’s details page too, so here +is the rule:

+
+
+ + + + + +
+
Tip
+
+When PATCHing flat fields on a cardholder, send back the same kind of JSON you got from a GET. +
+
+
+
+

Image PDFs and Base64

+
+

Here is a cutting from the details page of a cardholder with an image +PDF:

+
+
+
+
// GET /api/cardholders/{id}
+{
+  ...
+  "@datePDF": "2099-03-31T00:00:00Z",
+  "@Email": "a@b.com",
+  "@Mugshot": {
+    "href": "https://localhost:8904/api/cardholders/325/personal_data/8449"
+  }
+  ...
+}
+
+
+
+

Notice that the image PDF does not show in a cardholder’s details, +because they can be massive. Instead you get a URL. If you follow that +link you will see the image.

+
+
+

In order to send binary data in JSON (which cannot contain non-printable +characters), you have to encode it to Base64. This turns raw bytes into +a string of letters, numbers, plusses, and slashes (64 possible +characters), sometimes with equals signs on the end. It also increases +the size of the data by about a third. You can put the string between +quotes and send it like any other PDF:

+
+
+
+
// PATCH /api/cardholders/{id}
+{
+  "@photo": "Kilobytes+of+Base64+encoded+data==="
+}
+
+
+
+ + + + + +
+
Tip
+
+If you see a load of what looks like garbage ending with equals signs, it is probably Base64. +
+
+
+
+
+
+

Cards

+
+
+

Meaning credentials. In this section you will see how to give a +cardholder a card and modify existing cards.

+
+
+

Adding, updating, and deleting cards

+
+

Like all cardholder modifications, you do this with a PATCH to the +cardholder href. However a card is not a flat field: it is a member of +an array in the cardholder object called cards. To add an item to the +cards array, or change one, you pass in an object also called cards. +True to previous advice, we do all operations in one PATCH.

+
+
+

Borrowing from the API documentation:

+
+
+
+
+

The cards object can contain three arrays, named add, update, and +remove. Every element you put in those arrays should be in the card +schema that you see in a cardholder detail.

+
+
+

Each element of the add array will need a type member, at the very least. The only card field +that does not make sense here is href, because an href in a card block names an existing card to +change, but here you are creating one.

+
+
+

The example below adds two cards: one has nothing more than the type, so it will receive a computed +number and issue level, and blank from and until dates. The other is a mobile credential with a +custom initial state 'Pending sign-off'. You can tell it is a mobile credential because only they +have invitation blocks.

+
+
+

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 (making it valid forever).

+
+
+

The only field that makes sense in an element of the remove array is href.

+
+
+

Do not put the same href in both the update and remove arrays.

+
+
+
+
+

Here is the example. As well as adding two credentials, modifying a third, and removing another, it +authorises the cardholder and sets a PDF called employeeID just to remind you that you can combine +operations:

+
+
+
+
//PATCH /api/cardholders/{id}
+{
+  "authorised": true,
+  "@employeeID": "THX1139",
+  "cards": {
+    "add": [
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/354"
+        }
+      },
+      {
+        "type": {
+          "href": "https://localhost:8904/api/card_types/600"
+        },
+        "number": "Jock's iPhone 8",
+        "status": {
+          "value": "Pending sign-off"
+        },
+        "invitation": {
+          "email": "jock@example.com"
+        }
+      }
+    ],
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/97b6a24ard6d4500a9d",
+        "issueLevel": 2,
+        "until": ""
+      }
+    ],
+    "remove": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/cards/77e8affe7c7e4b56"
+      }
+    ]
+  }
+}
+
+
+
+

Notice how the hrefs of a card include the cardholder’s href and end +with a long identifier. That is because a card is a property of a +cardholder. Do not read anything more into it: treat it as opaque.

+
+
+
+

Don’t delete cards: disable them

+
+

That wasn’t a great example because generally, when you have reason to stop a card from working, you +want:

+
+
+
    +
  • +

    a permanent reminder of why you did it,

    +
  • +
  • +

    to prevent another operator assigning the same card number to them later (so that if someone finds +a card on the ground and tries it, it won’t open the building), and

    +
  • +
  • +

    to know who a lost card was assigned to in case it turns up again.

    +
  • +
+
+
+

Command Centre achieves the first two of these goals if you delete an old card but it is easier if +you leave it card in the system, non-functional. You can set its end date into the past or set its +state to one of the disabled states.

+
+
+ + + + + +
+
Warning
+
+Deleting cards loses information about it, and can reduce the security of your building.
+Disable them instead. +
+
+
+
+
+
+

Group memberships

+
+
+

In this section you will add your cardholder to an access group and +modify the membership.

+
+
+

Add an access group membership

+
+

You will need the href of your cardholder that you used in the +cardholder detail GET or the +cardholder PATCH, or that came +back from your POST when you created a +cardholder.

+
+
+

You also need the href of your access group. You can see all your access +groups by querying the access groups controller. Hint: GET /api then look +in the block called accessGroups. Extra hint: GET /api/access_groups.

+
+
+

When you have those two hrefs, substitute them into:

+
+
+
+
// PATCH /api/cardholders/325 (1)
+{
+  "accessGroups": {
+    "add": [
+      {
+        "accessGroup": {"href": "https://localhost:8904/api/access_groups/5388"} // (2)
+        , "from": "2017-01-31T02:11:00Z"
+        , "until": "2037-01-31T02:11:00Z"
+      }
+    ]
+  }
+}
+
+
+
+
    +
  1. +

    is the href of your cardholder.

    +
  2. +
  3. +

    is the href of your access group.

    +
  4. +
+
+
+

(Remember that the first line does not go into the body of your HTTP query, and your actual URL will +start with https:// with a host and port. Also note the alternative comma style: putting them on +the start of the line makes commenting them out easier.)

+
+
+

If you use the wrong access group identifier, or your operator does not +have 'Modify Access Control' on the access group, you will be told:

+
+
+
+
{
+  "message": "Invalid access group href: https://localhost:8904/api/access_groups/53888"
+}
+
+
+
+

When you get it right, the server will return 204 and next time you GET +your cardholder the result will contain:

+
+
+
+
// GET /api/cardholders/325
+{
+  ...
+  "accessGroups": [
+    {
+      "href": "https://localhost:8904/api/cardholders/325/access_groups/1069", // (1)
+      "accessGroup": {
+        "name": "Boney M",
+        "href": "https://localhost:8904/api/access_groups/5388"
+      },
+      "status": {
+        "value": "Active",
+        "type": "active"
+      },
+      "from": "2017-01-31T02:11:00Z",
+      "until": "2037-01-31T02:11:00Z"
+    }
+  ]
+}
+
+
+
+

The 'Cardholder access group' section of the cardholder API +documentation helps with interpreting that.

+
+
+

The marked URL is the href of the cardholder’s group memberrship, which is a link between the +cardholder (ID 325, in my case) and the access group (ID 5338). It starts with the href of the +cardholder, because it is a property of that cardholder and serviced by the cardholders controller, +but do not try to interpret it more. Certainly do not read anything into the number on the end +(1069), and do not be surprised if you have an item with the same ID.

+
+
+

The importance of /api and Identifiers in your app go into what you should not do with +hrefs.

+
+
+
+

Edit an existing group membership

+
+

Correct the URL of the cardholder and the access group membership in this PATCH:

+
+
+
+
//PATCH /api/cardholders/325
+{
+  "accessGroups": {
+    "update": [
+      {
+        "href": "https://localhost:8904/api/cardholders/325/access_groups/1069",
+        "from": "2027-03-09"
+      }
+    ]
+  }
+}
+
+
+
+

If it returns a 204, GET your cardholder again and look at its access +group memberships. The from date should have changed from 2017 to 2027, +and the membership href will be different.

+
+
+

The server changes the href after an update to prevent race conditions +when there are two operators active. It means the two of you cannot +change the group membership at the same time - the second one in will +fail. The advice, therefore, is to update your cardholder as soon as +possible after retrieving its details (footnote: probably good advice +for a fetch and update on any API).

+
+
+ + + + + +
+
Caution
+
+Do not cache the hrefs of links between items. They change with operator actions. +
+
+
+
+
+
+

Create a cardholder, cont.

+
+
+

Now that you have access groups, cards, and PDFs, you can create a fully +configured cardholder in one request. Here is an example that creates a +cardholder, sets a PDF called 'email', puts it in an access group (which +is necessary for the PDF to work), and gives them a card.

+
+
+
+
// POST /api/cardholders
+{
+  "firstName": "New", "lastName": "Cardholder",
+  "description": "Test cardholder",
+  "division": {"href":"https://localhost:8904/api/divisions/2"}
+  "useExtendedAccessTime": true,
+  "usercode": "1234",
+  "@email": "a@b.com",
+  "accessGroups": [
+    {
+      "accessGroup": {"href": "https://localhost:8904/api/access_groups/334"}, // (1)
+      "from": "2019-01-01"
+    }
+  ],
+  "cards": [
+    {
+      "type": {"href": "https://localhost:8904/api/card_types/342"}, // (1)
+      "number":"3162"
+    }
+  ],
+  "zzzcompetencies": [ // (2)
+    {
+      "competency": {"href": "https://localhost:8904/api/competencies/5394"},
+      "enabled": true,
+    }
+  ]
+}
+
+
+
+
    +
  1. +

    These identify an access group and card type for your new cardholder.

    +
  2. +
  3. +

    Never mind the competency yet. The zzz makes the server ignore it.

    +
  4. +
+
+
+

You will need to change the marked numbers to the IDs of an access group and a card type on +your system. You can get those with:

+
+
+
+
GET /api/access_groups
+
+
+
+

and

+
+
+
+
GET /api/card_types
+
+
+
+

In Chrome, those calls will look like +https://yourhost:8904/api/access_groups and +https://yourhost:8904/api/card_types.

+
+
+

An actual application would also find the href of the correct division, +but for today is it safe to assume that the href of the root division is +…​/divisions/2.

+
+
+

After changing the 334 and the 342 put the JSON into Postman and POST it to +/api/cardholders. It should return you the href of a new cardholder, as it did in +Create a cardholder.

+
+
+

The zzz is in there to stop the REST API trying to add a competency to +your new cardholder, which would fail because you have not created a +competency yet. There is nothing special about three 'Z’s — the server +just ignores anything it does not recognise.

+
+
+ + + + + +
+
Important
+
+The server will ignore fields it does not recognise. Beware of this, as you may think +your calls are succeeding when in fact they ard doing less than you want them to. +
+
+
+

That is more of an advantage that a disadvantage. It means we can write +clients that degrade gracefully on Command Centre servers that are not +the most recent version or are missing licences. Also, introducing typos +to the names of your JSON objects is a convenient way of commenting them +out. You can also prepend lines with //. It is not valid JSON but you +can get away with it for now.

+
+
+

Back to our example. If you want to create a cardholder with a +competency:

+
+
+
    +
  1. +

    make a competency in the Configuration Client,

    +
  2. +
  3. +

    find its href from the competencies controller (GET /api/competencies),

    +
  4. +
  5. +

    change your JSON (remove the zzz and change the 5394), and

    +
  6. +
  7. +

    try the POST again.

    +
  8. +
+
+
+

It should fail, complaining that you cannot have two cards with the same card number. Change the +3162 and try again (or change number to znumber and let Command Centre pick a card number for +you — probably 3163).

+
+
+
+
+

Coding considerations

+
+
+

If you don’t mind a return to theory, here are several things you should keep in mind when building +an integration against this API.

+
+
+

Recap

+
+
    +
  • +

    /api returns links to summary pages. Why that is important is in +The importance of /api.

    +
  • +
  • +

    At time of writing, the URLs of most summary pages end with the name of the +controller, such as cardholders or access_groups. Others end with their +specific purpose, such as card_types/assign, which returns the card types your operator can assign +to people.

    +
  • +
  • +

    Summary pages show you many items without much detail of each. You can add sorting and pagination +parameters. Cardholders, alarms and +events, and other items. Tell the API to sort its results by ID +because it is quicker, and more reliable when operators are changing the database. Unless you’re +writing a user app and really must have your results sorted by name.

    +
  • +
  • +

    You can also add filters to summary pages, turning them into search pages. See +Search for a cardholder and Event filters.

    +
  • +
  • +

    In v8.00+ you can add fields from the details page to the summary page +of items, and in 8.40+, events. Or you can specify the exact fields you +need, if you want to save traffic.

    +
  • +
  • +

    You walk the result set using links named next and previous.

    +
  • +
  • +

    Detail pages give you more on an item, but only one item at a time. Their URLs end with short +alphanumeric identifiers. A cardholder, for example. There is not much +use for detail pages in the API after v8.00, since you can add all their fields to the summary pages.

    +
  • +
  • +

    To create a cardholder, POST the +cardholders controller. The body of the POST is pretty much the same as +you get from a GET of an existing cardholder, but with fewer fields.

    +
  • +
  • +

    To update a cardholder, PATCH its href. +That includes adding cards.

    +
  • +
  • +

    If you are changing PDFs or flat fields, the body of the PATCH looks a +lot like what you got from a GET to the same URL. Put @-symbols on the +front of your PDF names.

    +
  • +
  • +

    If you are updating cards, lockers, access groups, relationships, +competencies, or operator groups, you will be sending arrays called +add, remove, and update inside objects called cards, lockers, +etc.

    +
  • +
+
+
+
+

Only one cardholder at a time

+
+

In all these flat field, card, and group membership examples you were +working on one cardholder at a time. That is the only way you can +operate, because the cardholder you are changing is named by the URL. If +you want to change many cardholders, you must do it in a loop.

+
+
+

On the upside, you can change everything about the cardholder in one +PATCH. In fact, it is most efficient to do so. While you can use DELETE +to remove one card or group membership or relationship at a time, you +will see much better throughput if you combine it with the other changes +for that cardholder and send them as one PATCH. The same applies to +creating a cardholder: it is much quicker to do it as one POST than as a +POST followed by one or more PATCHes. The other advantage is that any +one is atomic: all the changes you put in the body happen, or none of +them do. So:

+
+
+ + + + + +
+
Tip
+
+When creating a new cardholder, do it all in one POST.
+When modifying an existing cardholder, do it all in one PATCH. +
+
+
+
+

The importance of /api

+
+

Forget all the URLs you have seen so far, except the first, and do not +write them into your applications. The only address that your +application should have coded into it is /api. You can learn every +other address you need with a GET of that. It will return a table of +contents like this:

+
+
+
+
// GET /api
+{
+  "version": "7.90.0.0",
+  "features": {
+    "accessGroups": {
+      "accessGroups": {
+        "href": "https://localhost:8904/api/access_groups"
+      }
+    },
+    "accessZones": {
+      "accessZones": {
+        "href": "https://localhost:8904/api/access_zones"
+      }
+    },
+    "alarms": {
+      "alarms": {
+        "href": "https://localhost:8904/api/alarms"
+      },
+      "updates": {
+        "href": "https://localhost:8904/api/alarms/updates"
+      },
+      "divisions": {
+        "href": "https://localhost:8904/api/divisions/view_alarms"
+      }
+    },
+    "alarmZones": {
+      "alarmZones": {
+        "href": "https://localhost:8904/api/alarm_zones"
+      }
+    },
+    "cardholders": {
+      "cardholders": {
+        "href": "https://localhost:8904/api/cardholders"
+      },
+      "updateLocationAccessZones": {
+        "href": "https://localhost:8904/api/access_zones/update_cardholder_location"
+      },
+      "changes": {
+        "href": "https://localhost:8904/api/cardholders/changes"
+      }
+    },
+    "cardTypes": {
+      "cardTypes": {
+        "href": "https://localhost:8904/api/card_types"
+      },
+      "assign": {
+        "href": "https://localhost:8904/api/card_types/assign"
+      }
+    },
+    "competencies": {
+      "competencies": {
+        "href": "https://localhost:8904/api/competencies"
+      }
+    },
+    "doors": {
+      "doors": {
+        "href": "https://localhost:8904/api/doors"
+      }
+    },
+    "events": {
+      "events": {
+        "href": "https://localhost:8904/api/events"
+      },
+      "updates": {
+        "href": "https://localhost:8904/api/events/updates"
+      },
+      "eventGroups": {
+        "href": "https://localhost:8904/api/events/groups"
+      },
+      "divisions": {
+        "href": "https://localhost:8904/api/divisions/view_events"
+      }
+    },
+    "fenceZones": {
+      "fenceZones": {
+        "href": "https://localhost:8904/api/fence_zones"
+      }
+    },
+    "inputs": {
+      "inputs": {
+        "href": "https://localhost:8904/api/inputs"
+      }
+    },
+    "items": {
+      "items": {
+        "href": "https://localhost:8904/api/items"
+      },
+      "itemTypes": {
+        "href": "https://localhost:8904/api/items/types"
+      },
+      "updates": {
+        "href": "https://localhost:8904/api/items/updates"
+      }
+    },
+    "lockerBanks": {
+      "lockerBanks": {
+        "href": "https://localhost:8904/api/locker_banks"
+      }
+    },
+    "macros": {
+      "macros": {
+        "href": "https://localhost:8904/api/macros"
+      }
+    },
+    "outputs": {
+      "outputs": {
+        "href": "https://localhost:8904/api/outputs"
+      }
+    },
+    "personalDataFields": {
+      "personalDataFields": {
+        "href": "https://localhost:8904/api/personal_data_fields"
+      }
+    },
+    "roles": {
+      "roles": {
+        "href": "https://localhost:8904/api/roles"
+      }
+    }
+  }
+}
+
+
+
+

You should parse the features block of that page for the URLs of the +calls you need. It contains one block for each controller in the API: +cardholders, events, alarms, etc. Some of those blocks contain one more +block, in turn containing an href for the base call for that controller. +Other controllers (card_types) contain more than one, each containing an +href for a different call. For example, the events and alarms +controllers also provide a divisions call which lists the divisions in +which your operator has the privilege to see events and alarms, +respectively.

+
+
+

Do not code URLs into applications

+
+

Because Gallagher reserves the right to change them. If you start at +/api, your application will stay compatible through Command Centre +upgrades.

+
+
+

While it is tempting to hard-code a string /api/cardholders into +your application, it is not that much more development effort to get +that URL from the contents page and make your code forward-compatible. +Help yourself to the sample C#, starting with ClientManagerAsync.cs.

+
+
+
+
+

Identifiers in your app

+
+

There are two API identifiers: IDs and hrefs.

+
+
+

The short alphanumeric string that comes in a field called id is there purely for use in query +parameters, such as filters. Because you add them to URLs they have to be short and free of +punctuation, so we do not use the longer identifier that comes next. Event filters shows how to +use them to find events.

+
+
+ + + + + +
+
Caution
+
+Do not treat IDs as integers. They are alphanumeric. A future version may add letters. +
+
+
+

The URL that comes in a field called href is how you reference +objects, both as addresses in your own GETs, DELETEs, and PATCHes, but +also in the bodies of those requests and POSTs when you need to connect +two objects. When adding a card to a cardholder, for example, you need +to send the href for the new card’s card type. You would find that href +using the card_types controller. As another example, when connecting two +cardholders with a relationship, you need to PATCH the href of one of +the cardholders with the href of the role (from the roles controller) +and of the other cardholder (from a search of cardholders).

+
+
+

Some hrefs are meant to 404

+
+

Many hrefs will respond to a GET, but some exist purely for +identification: cards, relationships, and group memberships, for +example. Those hrefs are for use inside the body of a PATCH to modify a +cardholder. You can DELETE some of them but GETting one of these will +always return 404.

+
+
+
+

Many hrefs are dynamic

+
+

Some hrefs change regularly: access group membership IDs, for example, +change every time you modify the underlying membership, even if you only +change its end-date. For that reason you must start all changes to a +cardholder with a GET of that cardholder. That will give you up-to-date +hrefs for linked group memberships, roles, cards, etc., which you can +then use in the body of a PATCH.

+
+
+
+

You can use them to cross-reference cardholders in an integration

+
+

External systems will have their own user identifiers: staff or student +ID numbers, usernames, or national IDs. You might like to store that ID +in a PDF and use the PDF search (/api/cardholders?pdf_yyy=zzz, +described in the developer documentation) to find the href of your +cardholder when it comes time to synchronise. Then you do not have to +store a copy of the href.

+
+
+

That could be slow for large numbers of cardholders. Instead, you could +retain the href returned to you when you created your cardholder and use +that forever after. There would be no need for a PDF holding the +external identifier inside Command Centre.

+
+
+

However that is no use for cardholders that your integration did not +create. Plus you risk losing your cardholder if its href ever changes +(which could occur if another operator or integration deletes and +recreates it, or Gallagher changes the layout of hrefs).

+
+
+

The recommended option is a blend of the two: give every cardholder a +PDF containing their external ID and cache their href externally. If +your cache does not have it, or if using it returns a 400-level error, +refresh your cache using a PDF search.

+
+
+
+

Subtract the host and port then replace them with values from your integration’s configuration

+
+

You will note that every cardholder href begins with the scheme, host, +and port: https://localhost:8904 in these examples. I am going to +contradict earlier advice ever so slightly and suggest that you drop the +https://host:port from the front of an href before storing it, then +add the host and port from your integration’s configuration before using +it again. By doing that you give yourself the flexibility to change the +hostname or port without invalidating your cache of hrefs.

+
+
+

For example, for a cardholder with href +https://localhost:8904/api/cardholders/123, store +api/cardholders/123 in your database. The application must have +locahost and 8904 in its configuration somewhere (how else could it +make HTTP requests?), so when it comes to find that cardholder again, +prepend https://localhost:8904/ to the stored value. When your IT +people change the server’s hostname or shift the service to another +port, all you have to do is change your configuration. Which you had to +anyway.

+
+
+
+
+

Do not build your own hrefs from IDs

+
+

Buoyed with confidence gained following links around our API, you will +be tempted to store just the parts of hrefs that seem to matter and +reconstruct them later. In the interests of forward compatibility:

+
+
+ + + + + +
+
Caution
+
+Do not interpret href paths, and do not build your own. We change them. +
+
+
+

As a reminder, the parts of a URL relevant to us are the protocol +('scheme'), host, port, path, and query:

+
+
+
+
scheme://host:port/path?query
+
+
+
+

The scheme will always be HTTPS: you can take that as read. By all means, +replace the hostname and port number and add your own search parameters +to the query on the end of URLs you take from GET /api, but please do +not tinker with the path. Treat that as opaque.

+
+
+

For example, in version 8.10 the path to a locker was

+
+
+
+
/api/locker_banks/locker_bank_id/lockers/locker_id
+
+
+
+

In 8.20 it changed to

+
+
+
+
/api/lockers/locker_id
+
+
+
+

Client code that inserts a locker bank ID and locker ID into the +hard-coded string /api/locker_banks/{1}/lockers/{2} will work against +an 8.10 server but fail when the server upgrades to 8.20. Client code +that takes the href from a locker bank page will work with both +versions.

+
+
+
+

Sort by ID, and get all summary pages without delay

+
+

By default, item summaries arrive sorted by name. That can cause a race +condition:

+
+
+
    +
  1. +

    You get the first 1000 cardholders, sorted by name.

    +
  2. +
  3. +

    Another operator (or your own update) changes the name of one of those +cardholders to part of the alphabet you have not collected yet, or vice +versa.

    +
  4. +
  5. +

    The next 1000 cardholders will either contain one you already +received, or will skip one you did not.

    +
  6. +
+
+
+

There are three things you can do to reduce this risk:

+
+
+
    +
  • +

    Sort by ID. Command Centre does not recycle IDs so no cardholder can +slip into part of the database you have already extracted.

    +
  • +
  • +

    Collect hrefs from the summary pages, following the next link until it +no longer arrives, before using any of them. This means you collect +everyone in the minimum possible time, and if you do update your +cardholders you will not mess in your own yard.

    +
  • +
  • +

    Get thousands at a time so that you make fewer calls.

    +
  • +
+
+
+
+

Monitoring for cardholder changes is much better in 8.30

+
+

Version 7.90 supports change tracking through the events API. If you +filter for operator events you will be informed of every change using +long polls (below): the href of the affected cardholder or access group +will be in the event. In 8.00+ the href of the operator who made the +change will also be in the event.

+
+
+

Version 8.30 adds a call to the cardholders controller that makes +synchronising them much simpler. It can tell you which fields changed on +a cardholder and what their values were before and after the change, and +what their current values are. All the details are in a section called +"Cardholder changes" in the developer documentation but here is a +quick run-down:

+
+
+
    +
  1. +

    Send a GET to request a bookmark to the current head of the list of +cardholder changes.

    +
  2. +
  3. +

    Synchronise your system with Command Centre using other cardholder +methods. It does not matter how long this takes.

    +
  4. +
  5. +

    GET the bookmark you received previously. That will send you all the +changes that happened since then, plus a new bookmark.

    +
  6. +
  7. +

    Process those changes, if there were any. Sleep if there were not.

    +
  8. +
  9. +

    Go back to step 3.

    +
  10. +
+
+
+

To reduce the work you have to do and chatter on the wire using filter +and fields query parameters. filter limits the changes you receive +to those that you’re particularly interested in (you might not care +about anything except changes to PDFs, for example), and fields lets +you request more or less data about each change and its cardholder.

+
+
+
+

Long polls

+
+

A long poll is a way for HTTP servers to send updates to interested +clients. The client registers its interest by sending an HTTP GET, and +the server pushes to the client by responding when something of interest +occurs.

+
+
+

Using telephone calls as an analogy, a traditional poll would have the +client calling the server, the server answering, then hanging up +immediately if there was nothing to report. The client would then need +to wait a time and try again.

+
+
+

If the telephone server supported long polls, however, it would leave +the incoming call ringing until it had something to say. The client +would carry on about its business until the server picked up. After +hearing the server’s response the client would call back when it wished. +Immediately if it was in a particular hurry.

+
+
+
+

Benchmarks

+
+

These are the results of informal performance tests of Command Centre +7.90 running on reasonably capable hardware.

+
+
+

You will not achieve these numbers without following the advice in the +'efficiency tips' sections of the developer documentation.

+
+
+

Extracting events

+
+

Sustained an average of two to four thousand per second from a database +of four million.

+
+
+
+

Extracting 12,000 cardholders

+
+

Extracting their cards, access groups, and PDFs took three to four +minutes on a v7.90 server. The process was to request a summary page of +10,000 cardholders, then the remaining 2,000, then iterate through all +their hrefs, getting their details pages one by one.

+
+
+

That is a poor approach these days. Extracting the same fields for the +same 12,000 cardholders took 12 seconds on the same server running +v8.00. This process used the fields parameter to add cards, access +groups, and PDFs to the summary page so that the test did not have to +get any detail pages.

+
+
+
+

Extracting 200,000 cardholders

+
+

Now on 8.30 and different hardware, extracting the names of 200,000 +cardholders took one minute with top=1000&sort=id, or 28 minutes +without. That is how important those query parameters are. Use them!

+
+
+

Part of the three-times speedup from the previous test will be due to the absence +of PDFs: they can be expensive to extract.

+
+
+
+

Creating cardholders

+
+

Ten thousand took an hour. The test added a cardholder with a card and a +handful of group memberships and PDFs.

+
+
+
+
+
+
+

Advanced events

+
+
+

Event filters

+
+

The developer documentation is authoritative on how to restrict your +event results, but here is an introduction.

+
+
+

You can filter by the occurrence date/time, the source item, the event’s division (which is almost +always the source’s division), the event’s type, the type’s group (all event types are grouped, and +picking a group is synonymous with picking a few types), or the event’s cardholder.

+
+
+

In 8.70 and later you can limit the events to those related to a particular item. For example, by +specifying a cardholder’s ID you will receive the same events that would appear in Command Centre’s +activity report for that cardholder.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To filter by …Add a query parameter called…

Event type

type

Event type group

group

Cardholder

cardholder

Source

source

Division

division

Date

after and / or before

Related item

relatedItem, and be running 8.70 or later.

+
+

For example, to find all card events ('access granted', 'access denied', +etc.):

+
+
+
+
GET https://localhost:8904/api/events?group=23
+
+
+
+

The API documentation shows you where the 23 comes from.

+
+
+

To watch two cardholders:

+
+
+
+
GET https://localhost:8904/api/events?id=325,8445
+
+
+
+
+

IDs to use in filters

+
+

To keep the query strings manageable, these filters take short, +alphanumeric strings as IDs rather than the URLs that the API generally +uses for identifying items. At the version of writing (8.70) these IDs are low numbers, +but we reserve the right to introduce letters in the future, so do not +interpret them as integers.

+
+
+

These are your options for finding the IDs you need to build a filter +string:

+
+
+
    +
  • +

    look at one of the events you want in the API. Everything you can filter by is there;

    +
  • +
  • +

    look at /api/events/groups for event types and their +groups;

    +
  • +
  • +

    look at /api/cardholders for cardholders;

    +
  • +
  • +

    look at /api/items for all other items, using a type filter of its own from looking at +/api/items/types;

    +
  • +
  • +

    if running v8.00 or later, look at the controllers for doors, outputs, alarm zones, access zones, +and fence zones, linked from /api;

    +
  • +
  • +

    If running v8.10 or later, look at the inputs controller.

    +
  • +
+
+
+

For example, to find all your doors you would

+
+
+
+
GET https://localhost:8904/api/doors
+
+
+
+

(after getting that URL from GET /api, of course)

+
+
+

The doors controller arrived in 8.00, so if you have 7.90 or older you would

+
+
+
+
GET https://localhost:8904/api/items?type=11
+
+
+
+

That 11 came from

+
+
+
+
GET https://localhost:8904/api/items/types
+
+
+
+
+

Filtering by date

+
+

Even though the before and after fields are only accurate to a second, +filtering by date is 'smart' for reports: the result set will not +include events that occurred during the before second. For example, +before=2019-01-01T00:00:00Z will not return you any events from 2019 or later years. +Pass the before parameter for one report as the after parameter of +the next. You never need to use 23:59:59, and there is no risk of +missing an event that happens in the last second, or in a leap second.

+
+
+

All date-times should be in ISO-8601. If you omit fields (such as +minutes or seconds) Command Centre will assume sensible defaults, but +the best advice is to be explicit (especially about the time zone).

+
+
+ + + + + +
+
Important
+
+Put a timezone specifier in all date-times! Otherwise the outcome will depend on settings +on the server. +
+
+
+
+

Adding PDF values to the cardholders in events

+
+

Card events such as 'access granted' use the door as the source but also +have a related cardholder. The event JSON includes the cardholder’s name +and href, but if you want to use your own identifiers for cardholders +you can also ask for a PDF to come out with the event. Do that by adding +fields=defaults,cardholder.pdf_XXXX where XXXX is the ID of the PDF. +Find that ID with a query to /api/personal_data_fields, adding +?name="your_pdf_name" if you want Command Centre to do the searching +for you.

+
+
+

In order to see that PDF, your REST operator will need the appropriate +privileges. Otherwise the event will come out without the PDF. 'View +Cardholder' on the cardholder might not be enough: while PDFs are +visible by default, an operator can hide them, in which case your REST +client’s operator group will need to override that to readable or +read/write.

+
+
+
+

Writing an interactive event viewer

+
+

If I was writing an interactive application to monitor events as they +occurred, while also allowing browsing the event history, I would get +the most recent—​enough to fill a screen—​with:

+
+
+
+
GET /api/events?previous=true&top=20
+
+
+
+

Then I would set an asynchronous task waiting on the updates link, +which would return with new events as they happened.

+
+
+

At the same time I would follow the next and previous links to +collect more events as my user scrolled back and forth.

+
+
+
+

Worked example: reading alarms

+
+

What follows is a series of calls that collect alarms from the 7.80 +version of the alarms API. There may be extra fields in later versions +of Command Centre.

+
+
+

Collecting all unprocessed alarms with one active forced door

+
+

The initial HTTP GET of http://localhost/api/alarms returns all +unprocessed alarms: a bad login, a network problem, and two forced +doors, in this example. The second forced door is still open, so the +alarm is active and instead of links for processing it we have links for +force processing it, because you are not really meant to process active +alarms.

+
+
+

Note they are in the order that they arrived at the server, not the +order they happened. Interesting pieces are in bold face.

+
+
+
+
{
+  "alarms": [
+    {
+      "href": "http://localhost:8904/api/alarms/289",
+      "id": "289",
+      "time": "2016-11-10T14:17:00",
+      "message": "Operator logon failed for FT Workstation on GNZ-PC1302",
+      "source": { "name": "FT Workstation on GNZ-PC1302" },
+      "type": "Operator Logon Failed",
+      "priority": 3,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/289/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/289/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/289/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/289/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/289/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/289/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/296",
+      "id": "296",
+      "time": "2016-11-10T13:58:16",
+      "message": "Fat controller - Command Centre comms interrupted",
+      "source": { "name": "Fat controller" },
+      "type": "Comms failed to Command Centre",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/296/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/296/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/296/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/296/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/296/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/296/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/301",
+      "id": "301",
+      "time": "2016-11-10T14:18:27",
+      "message": "Warehouse door has been forced.",
+      "source": { "name": "Warehouse door" },
+      "type": "Forced Door",
+      "priority": 8,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/301/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/301/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/301/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/301/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/301/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/301/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/306",
+      "id": "306",
+      "time": "2016-11-10T14:21:41",
+      "message": "Front door has been forced.",
+      "source": { "name": "Front door" },
+      "type": "Forced Door",
+      "priority": 8,
+      "state": "unacknowledged",
+      "active": true,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/306/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/306/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+      "forceProcess": { "href": "http://localhost:8904/api/alarms/306/process" }
+    }
+  ],
+  "updates": { "href": "http://localhost:8904/api/alarms/updates?id=306" }
+}
+
+
+
+
+

Collecting updated alarms after closing the door

+
+

Next we close the front door, the kicking in of which caused alarm 306, +and GET the updates URL at the end of the previous result, +http://localhost/api/alarms/updates?id=306. Because the alarm is no +longer active we do not have a link for force-processing it; instead we +have links for processing it normally with or without comments.

+
+
+
+
"updates": [
+  {
+    "href": "http://localhost:8904/api/alarms/306",
+    "id": "306",
+    "time": "2016-11-10T14:21:41",
+    "message": "Front door has been forced.",
+    "source": { "name": "Front door" },
+    "type": "Forced Door",
+    "priority": 8,
+    "state": "unacknowledged",
+    "active": false,
+    "division": { "href": "http://localhost:8904/api/divisions/2" },
+    "view": { "href": "http://localhost:8904/api/alarms/306/view" },
+    "comment": { "href": "http://localhost:8904/api/alarms/306/comment" },
+    "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+    "acknowledge": { "href": "http://localhost:8904/api/alarms/306/acknowledge" },
+    "processWithComment": { "href": "http://localhost:8904/api/alarms/306/process" },
+    "process": { "href": "http://localhost:8904/api/alarms/306/process" }
+  }
+],
+"next": { "href": "http://localhost:8904/api/alarms/updates?id=306.1" }
+
+
+
+
+

Updating after cutting power

+
+

This is the result of http://localhost/api/alarms/updates?id=306.1 (the next link from the +previous results) after cutting power to the controller and waiting a minute for Command Centre to +raise an alarm about it. Nothing has changed on the previous alarms so they do not come out.

+
+
+
+
"updates": [
+  {
+    "href": "http://localhost:8904/api/alarms/308",
+    "id": "308",
+    "time": "2016-11-10T14:35:21",
+    "message": "Controller \"Fat controller\" Offline.",
+    "source": { "name": "Fat controller" },
+    "type": "Controller Offline",
+    "priority": 6,
+    "state": "unacknowledged",
+    "active": true,
+    "division": { "href": "http://localhost:8904/api/divisions/2" },
+    "view": { "href": "http://localhost:8904/api/alarms/308/view" },
+    "comment": { "href": "http://localhost:8904/api/alarms/308/comment" },
+    "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+    "acknowledge": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+    "forceProcess": { "href": "http://localhost:8904/api/alarms/308/process" }
+  }
+],
+"next": { "href": "http://localhost:8904/api/alarms/updates?id=308" }
+
+
+
+
+

Updating after restoring power

+
+

Next we GET http://localhost/api/alarms/updates?id=308 (the next +link from the previous results, again) after restoring power to the +controller and waiting for it to come online.

+
+
+

The 'controller offline' alarm (ID 308) has changed to inactive since +the controller has reappeared on the network.

+
+
+

The 'low power' alarm has arrived from the controller carrying a timestamp from when it lost mains +power, while it was running on internal reserve power, which was one minute earlier than the +'controller offline' alarm.

+
+
+

Bringing up the rear is another alarm that the controller generated when +it restarted.

+
+
+
+
{
+  "updates": [
+    {
+      "href": "http://localhost:8904/api/alarms/308",
+      "id": "308",
+      "time": "2016-11-10T14:35:21",
+      "message": "Controller \"Fat controller\" Offline.",
+      "source": { "name": "Fat controller" },
+      "type": "Controller Offline",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/308/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/308/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/308/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/308/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/308/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/310",
+      "id": "310",
+      "time": "2016-11-10T14:34:01",
+      "message": "Fat controller - power low.",
+      "source": { "name": "Fat controller" },
+      "type": "Controller power low",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/310/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/310/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/310/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/310/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/310/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/310/process" }
+    },
+    {
+      "href": "http://localhost:8904/api/alarms/313",
+      "id": "313",
+      "time": "2016-11-10T14:35:49",
+      "message": "Controller \"Fat controller\" restarted after power failed.",
+      "source": { "name": "Fat controller" },
+      "type": "Power failed",
+      "priority": 6,
+      "state": "unacknowledged",
+      "active": false,
+      "division": { "href": "http://localhost:8904/api/divisions/2" },
+      "view": { "href": "http://localhost:8904/api/alarms/313/view" },
+      "comment": { "href": "http://localhost:8904/api/alarms/313/comment" },
+      "acknowledgeWithComment": { "href": "http://localhost:8904/api/alarms/313/acknowledge" },
+      "acknowledge": { "href": "http://localhost:8904/api/alarms/313/acknowledge" },
+      "processWithComment": { "href": "http://localhost:8904/api/alarms/313/process" },
+      "process": { "href": "http://localhost:8904/api/alarms/313/process" }
+    }
+  ],
+  "next": { "href": "http://localhost:8904/api/alarms/updates?id=313" }
+}
+
+
+
+

That illustrates how events' occurrence times can be out of order when the devices sending them to +the database are out of sync.

+
+
+
+
+
+
+

Client-side certificates

+
+
+

First see Authentication and encryption certificates for what certificates are and the +difference between server certificates and client certificates.

+
+
+

This section deals with the client certificate check that happens if you +left 'Do not require pinned client certificates' off in the 'Web +Services' property tab of an 8.40 server, or if you entered a thumbprint +into a REST Client item in 8.50 or later.

+
+
+

The process in The request process shows that +if you have not disabled client certificate checking, the server does it +after extracting the API key. If the server does not have the client’s +certificate pinned to the REST Client item with that API key, it will +reject the request and raise an alarm:

+
+
+
+
A REST connection was attempted with an invalid client certificate
+
+
+
+

That alarm will be at the same priority as a controller disappearing off +the network, which should cause a stir, so try not to do it in +production.

+
+
+

The next two sections should help you decide whether to use the feature. +The sections following those contain sample command lines that you can +paste into a shell on your clients to create client certificates on disk +or in the Windows certificate store. If you receive syntax errors, you +may have an old version of the software (I have had problems with +New-SelfSignedCertificate on Windows) or the hyphens may not be +hyphens: they may come through as dashes, which look very similar to us +but not to shells. You may have to re-type them.

+
+
+

The bold in the sample command lines reduce the protection around your +private key. That may be acceptable in a development environment but for +proper security in a production environment you should omit the bold +parts.

+
+
+

What client certificate checking does

+
+

When not disabled by the checkbox in the server +properties, Command Centre requests proof from the client that it has the private key that matches a +public key that the server has configured into it (pinned). A public key is hundreds of bytes so you +don’t want to paste the whole thing into Command Centre’s configuration, and we do not want to +compare all those bytes for each request, so you enter a shorter form instead. It is known as the +certificate’s thumbprint or fingerprint and is a cryptographic hash of the whole +certificate. It is impossible for a client to send a fake certificate with a thumbprint that looks +real.

+
+
+

Certificates can also contain a chain of trust linking the certificate +back to a trusted authority. A client uses a server certificate’s chain +of trust to check the identity of the server that responded to its +request. It does not work in reverse: servers do not check that part of +a client’s certificate. You would not have pasted the certificate’s +thumbprint into Command Centre if you did not trust it.

+
+
+

When you enter a thumbprint into a CC REST Client’s property page in the +Configuration Client or leave the checkbox we are covering next +unchecked in the server properties, you are saying that only the caller +who has the matching private key is allowed to use that REST Client. In +other words, the client software must possess two secrets that the +server can verify: the API key and the private key.

+
+
+

Before getting into why you want all this checking happening, we should +cover how to turn it on. Or, since the product ships with it turned on, +why you should not turn it off.

+
+
+

The mysterious client certificate checkbox

+
+

In 'Server Properties', which you get to in the Configuration Client +starting with the 'File' menu or the top item in your hardware tree, +there is a tab called 'Web Services'. In there, in the box headed by +'Enable REST API', is a checkbox. In 8.40 it was called 'Require pinned +client certificates'. In 8.50 it is called 'Enable REST Clients with no +client certificate'.

+
+
+

It behaves like this:

+
+ +++++ + + + + + + + + + + + + + + + + + + + +
8.40'Do not require pinned client certificates' off'Do not +require pinned client certificates' on

Connection attempt to a REST Client item with a certificate +thumbprint configured

Client certificate checked

Client certificate +ignored, connection accepted

Connection attempt to a REST Client item without a certificate +thumbprint configured

Connection rejected

Connection accepted

+
+

In 8.50, the top-right quadrant changed.

+
+ +++++ + + + + + + + + + + + + + + + + + + + +
8.50'Enable REST Clients with no client certificate' off'Enable +REST Clients with no client certificate' on

Connection attempt to a REST Client item with a certificate +thumbprint configured

Client certificate checked

New in 8.50: client +certificate checked

Connection attempt to a REST Client item without a certificate +thumbprint configured

Connection rejected

Connection accepted

+
+

That change will have negatively affected sites that had 'Do not +require…' turned on, but also had thumbprints (uselessly) configured +into their client items. However there is a huge upside to the change: +in 8.50 or later a site can have some clients using client certificates +and some not.

+
+
+
+
+

Why use client certificates?

+
+

To make it harder for an attacker to masquerade as a legitimate REST +client.

+
+
+

To do that, they must obtain your API key at the very least. There are +more barriers that you can put up:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + +
If you:…the black-hat will then have to:

use a firewall (Windows or hardware)

be on the server network.

use an IP filter

spoof the source IP.

pin your client’s certificate

have a copy of the client’s private key.

limit your application privileges

settle for less access.

+
+

Pinning a client certificate is one more hoop an attacker has to jump +through.

+
+
+

You should make viewing your private key very difficult for anything +that does not need it. Do not leave it in the filesystem for anyone to +read! If you are running on Windows, you should use the certificate +store. If you are on another O/S, protect the key while it is on disk +with filesystem permissions and by encrypting it with a password hidden +in your application.

+
+
+
+

Create a certificate and record the thumbprint in CC

+
+

The following commands create a client certificate. You need to run them on the +system that will be running your REST client. That may not be your Command +Centre server.

+
+
+

Using OpenSSL tools

+
+

This method works equally well on Unix-like or Windows systems with OpenSSL +installed, but the later sections might serve Windows people better because +they show how to put the certificate directly into the Windows certificate +store.

+
+
+ + + + + +
+
Warning
+
+The OpenSSL commands in this section put the private key on disk, +which should make you a bit nervous if you are doing it in production. On +a Unix-like system you could do it in a mode-0700 folder on a filesystem +that is not backed up and is cleared during a reboot, such as /tmp. +
+
+
+
+
openssl req -x509      \
+  -newkey rsa:4096     \
+  -sha256              \
+  -nodes               \
+  -keyout rest.pem     \
+  -out rest.pem        \
+  -subj "/CN=RESTtest" \
+  -days 3650
+
+
+
+

Notice the rsa:4096: it produces a four-kilobit key, which is huge. It might be +overkill for development, but it’s nice to have the option.

+
+
+

Its name will be 'RESTtest'. You should come up with a name related to your application’s purpose +because diagnising certificate problems is a tough enough job without ambiguous certificate names +muddying the waters even more.

+
+
+

Again, the bold part is reducing your security. In this case, +the -nodes option (footnote: it means 'no DES'. It is not the plural +of 'node') means there is no password on the private key. Anyone could +read it from the rest.pem file, so in a production environment you should omit +the -nodes option and type in a password (a really good one) when +openssl req prompts you.

+
+
+

To get the thumbprint for Command Centre:

+
+
+
+
$ openssl x509 -fingerprint -in rest.pem -noout
+
+
+
+

If you protected the PEM with a password, openssl x509 will ask you +for it.

+
+
+ + + + + +
+
Note
+
+The 20 bytes that come out are what you paste into the REST Client +item in Command Centre. +
+
+
+

Now you need to add the certificate to the clients that need it. If you use Postman, +see Use the certificate in your client. If you use +Chrome on Windows, you need to add it to the certificate store with +these two commands:

+
+
+
+
openssl pkcs12 -export -in rest.pem -out rest.pfx -passout pass:
+explorer rest.pfx
+
+
+
+

The first command converts the PEM file into a file format that Windows +prefers. The -passout pass: option means it will not put a password on +it, so it is just as dangerous as the PEM file.

+
+
+

The second line will open rest.pfx in Explorer (the same as +double-clicking it) to import it into the certificate store. The default +options are good so you can click Next to let it use the current user, +determine the certificate store automatically, and mark the private key as +not exportable).

+
+
+

Finally, for goodness’ sake, protect the rest.pem and rest.pfx +files. Preferably delete them. Even better, use an eraser utility.

+
+
+
+

Using Windows tools

+
+

If your client is on a Windows host there are two more ways to create a +certificate and place it into Windows’s certificate store. Obtaining the +private key from there is easy for your client program but difficult for +anyone else, Microsoft assures us.

+
+
+
Powershell
+
+

The topic 'Creating the Client Certificate' in the Configuration +Client’s online help contains instructions for doing it in a PowerShell +with New-SelfSignedCertificate. Handily, it prints the thumbprint to +the console so you can copy it into Command Centre. You should try that +first, since it is simplest. But New-SelfSignedCertificate is not +present on all versions of Windows, so here is an alternative using…

+
+
+
+
makecert
+
+

…which has been around for longer.

+
+
+
    +
  1. +

    Run a developer command prompt as administrator. If you do not have a developer command prompt, +try a regular command prompt (as administrator).

    +
  2. +
  3. +

    In it:

    +
    +
    +
    makecert ignoreme.der
    +         -a sha1
    +         -ss My
    +         -sky signature
    +         -pe
    +         -len 2048
    +         -n "CN=RESTClientCert"
    +         -sr CurrentUser
    +
    +
    +
    +

    That will create a certificate and place it in your certificate store +with a copy on disk.

    +
    +
    +

    Its name will be 'RESTClientCert'. As previously indicated, you should come up with an informative +name for your certificates so that the brave souls diagnosing your TLS issues have something to +go on.

    +
    +
    +

    The -pe marked the key as exportable. More on that later.

    +
    +
  4. +
+
+
+

You do not need to keep the file ignoreme.der, but the easiest way to +get the thumbprint of your new certificate is to open ignoreme.der by +double-clicking on it in Explorer, go to the Details tab, scroll to the +bottom, and click the thumbprint. You could then skip the next three +steps, but when starting out it is a good idea to perform these steps to assure yourself +that makecert put your new certificate where it should have.

+
+
+
    +
  1. +

    Run mmc, add the Certificates snap-in to manage "My user account," +open it and then expand your "Personal" certificates.

    +
  2. +
  3. +

    Ensure you can see a certificate called (issued to) 'RESTClientCert' in there. +This is the cert you will pick for your browser later.

    +
  4. +
  5. +

    Double-click it, go to the Details tab, scroll to the bottom, and +click the thumbprint.

    +
  6. +
+
+
+ + + + + +
+
Note
+
+Those are the 20 bytes / 40 characters that you paste into the REST Client item in Command Centre. +
+
+
+
+
Aside: other ways of calculating the thumbprint
+
+

For your information, here are three more command-line options for +extracting the thumbprint from the DER file if you didn’t get it above. +They all do the same thing. Use whichever works for you:

+
+
+
+
openssl x509 -in ignoreme.der -inform der -noout -fingerprint
+openssl sha1 ignoreme.der
+sha1sum ignoreme.der
+
+
+
+

As you can see from the last two, a certificate thumbprint is really +just the SHA1 hash of the certificate when it is stored in a DER file.

+
+
+ + + + + +
+
Caution
+
+Some utilities, inluding openssl, print a thumbprint with colons separating each octet. +Take the colons out before pasting that into the REST Client item, otherwise your client will not be +able to make calls through the cloud API gateway. +
+
+
+
+
Marking keys as exportable
+
+

The -pe option to your makecert command above marked your private +key as exportable.

+
+
+

You can mark it as not exportable so that the standard utilities will not +be able to get it out of the certificate store. That sounds like a good +idea, because (while there are programs out there that export +non-exportable certificates) anything you can do to make the black-hat’s +job harder is a win.

+
+
+

If you used the command New-SelfSignedCertificate in Windows +Powershell (using the instructions in the Configuration Client’s user +guide), you can mark the certificate not exportable by adding +-KeyExportPolicy NonExportable to the command line.

+
+
+

The trouble is that in order to use your client certificate in Postman, +you have to export the key.

+
+
+

If you used makecert, remove the -pe and your new key will not be +exportable.

+
+
+
+
+

Extract the certificate and private key from the Windows store to disk

+
+

If you are going to use Postman you need to give it files containing +your private key and certificate, but if you are on Windows and used one of the Windows +utilities to create a new certificate in the certificate store, +you will not have the private key on disk. You will need to extract it.

+
+
+
    +
  1. +

    Run mmc. Certificates → Current User → Personal → Certificates.

    +
  2. +
  3. +

    Right-click your certificate → All tasks → Export…
    +Select the option to export the private key. Give it a password, +otherwise openssl cannot decrypt it.
    +It does not matter what you do with the other certificates, so leave the +defaults set.
    +Export it to a .pfx file on disk.

    +
    +

    That PFX file is partly secure because you put a password on it, but I expect +that password was very short so, again, be careful what you do with that +file.

    +
    +
  4. +
  5. +

    For old versions of Postman you may have to convert that PFX into a +file it understands. The current version of Postman does not need this:

    +
    +
    +
    $ openssl pkcs12 -in restexported.pfx –out rest.pem –nodes
    +
    +
    +
  6. +
+
+
+

It will ask you for the password you picked for the export. It will put +the certificate and the private key in the PEM file, unencrypted +(because of -nodes). It is plain text: you can look at it in Notepad.

+
+
+
+

An easy (but not so secure) way to discover your client certificate’s thumbprint

+
+

Create a client certificate using one of the methods above and use it in +an API call. The server should raise an alarm, complaining that 'a REST +connection was attempted with an invalid client certificate'. The rest +of that message will tell you which REST Client Item you need to put the +thumbprint on, and the alarm’s details string will contain the thumbprint +itself.

+
+
+

If the thumbprint is (null), your client did not send a certificate +at all.

+
+
+

Otherwise, copy the thumbprint straight out of there and paste it into +the item. The next time you try your call the server should not complain +about the certificate.

+
+
+

The reason this method is not so secure is that you might not be sure that +the alarm was yours. Someone else may have hit the API before you did.

+
+
+
+
+

Use the certificate in your client

+
+

Postman

+
+

The standalone version of Postman cannot read certificates out of the +certificate store (footnote: the Chrome extension can, but that version of +Postman is no longer under development).

+
+
+

Go to the cog → Settings → Certificates. Add the certificate that +Postman should use when talking to your server and port. Where Postman +asks for the CRT file, give it the file containing the certificate. Where +Postman asks for the key file, give it the file containing the private key. +They will both be the same file if you followed the example above.

+
+
+

If you protected your private key with a password (a good idea, but turned +off by -nodes), give it to Postman.

+
+
+
+Tell Postman which client cert to use +
+
Figure 18. Tell Postman which client cert to use
+
+
+

Now Postman will use that certificate when it talks to Command Centre. +If you put the certificate’s thumbprint on the REST Client item with the +API key Postman is using, you can turn on pinned certificates in the +server properties and Postman will still be able to connect.

+
+
+

You can leave Postman using this certificate no matter whether CC has +pinned certificates turned off or on: it does no harm.

+
+
+
+

Chrome

+
+

When you first try to connect to Command Centre using Chrome it will +give you a list of certificates in the store and ask you which to use. +Select the one you just put there.

+
+
+
+

wget

+
+
+
$ wget                                                                        \
+    --no-check-certificate                                                    \
+    --certificate=your_pem_file                                               \
+    --header="Authorization: GGL-API-KEY your-API-key"                        \
+    https://localhost:8904/api
+
+
+
+

The --no-check-certificate turns off client-side checking of the +server certificate.

+
+
+

Careful: my version of wget does not complain if it cannot read the +certificate file.

+
+
+
+

curl

+
+
+
$ curl                                                                        \
+    --verbose                                                                 \
+    --insecure                                                                \
+    --cert your_pem_file                                                      \
+    --header "Authorization: GGL-API-KEY your-API-key"                        \
+    https://localhost:8904/api
+
+
+
+

The --insecure turns off client-side checking of the server +certificate.

+
+
+

I found --verbose necessary to see error codes.

+
+
+
+
+
+
+

Server-side certificates

+
+
+

A client in a production environment should refuse to talk to a server that it does not recognise. +"Recognising" a server means either

+
+
+
    +
  • +

    having a copy of its certificate on the client already (which is the same as the client +certificate pinning covered in the previous section, only it is the client doing it to the server +instead of the server doing it to the client), or

    +
  • +
  • +

    having a trusted Internet authority’s signature on the certificate.

    +
  • +
+
+
+ + + + + +
+
Warning
+
+Production clients should check server certificates. If not, an attacker could +masquerade as the server. The client would send it its API key to the fake server, and the attacker +could—​if the real server was not checking client certificates—​use it for calls of its own. +
+
+
+

But if you are working in a development rig and your server does not have a signed certificate, you +might like to tell your client to skip the server certificate check, as you told Chrome and Postman +to do earlier.

+
+
+

If you will pardon a very brief dive into source code, here is one way to do it in a C# client:

+
+
+
+
ServicePointManager.ServerCertificateValidationCallback = delegate (
+    object s,
+    X509Certificate certificate,
+    X509Chain chain,
+    SslPolicyErrors sslPolicyErrors)
+  { return true; };
+
+
+
+

If you do not have control of the client application, or you wish to check the server certificate +(which is advisable), here are a couple of approaches to making the check succeed.

+
+
+

You only need one of these.

+
+
+

Pin the server certificate on the client

+
+

Go to Server Properties in Command Centre’s configuration client, then Web Services → Manage +Certificates, and click View on whichever type of certificate you are using. Go to the Details tab +in the window that appears, then choose Copy to File. You have a few choices for the format to +export it to. DER is good for Windows machines, but a PFX—​if that option is enabled—​might be +more widely accepted on non-Windows systems. If you are ever asked if you want to export the private +key, just say "no".

+
+
+

Once you have your certificate file on disk, copy it to the client +machine. Provided you didn’t let a private key get in there, it is not a +secret.

+
+
+

How you install it on the client depends on the client. On a Windows +box, it may be as simple as double-clicking it. The Certificate Import +Wizard will ask you where to install it: you probably want Trusted Root +Certificate Authorities. That is a bit of a sledgehammer, because it +makes not only your REST client but every client on that host trust that +certificate. Plus, they will trust any other certificate signed by it. +But it will get you going.

+
+
+

You have more options if you are writing your own client. You might like to have a copy of the +server’s certificate on disk to compare against. A certificate is large, so the usual approach is +to record a hash of it instead. It is not a secret but you must prevent anything from changing it, +because when your client connects to the server you want to hash the server’s certificate again and +compare it to the value you have on record. If they are not the same, don’t trust the server.

+
+
+

Windows calls the SHA1 hash of certificate its thumbprint, and presents it in the Details tab of +the window the first paragraph took you to: Server Properties → Web Services → Manage +Certificates → View → Details. You’ll find the thumbprint at the bottom of the field list (you +will have to scroll).

+
+
+
+

Buy a signed certificate for the server

+
+

If that method does not +suit, perhaps because you do not have control of the clients, you could +buy a "real" certificate for your CC server. One drawback is that +signatures eventually expire, requiring you to do this every year or +three. Another is that you must buy a new certificate if you change the +name of your server. So, use a DNS alias.

+
+
+

This is nothing new for REST APIs: it is the process that every web +site owner goes through. If you want to sell scones and Toby mugs from +www.itsabritishthing.com, for example, you:

+
+
+
    +
  1. +

    generate a certificate for www.itsabritishthing.com,

    +
  2. +
  3. +

    generate a certificate signing request (a CSR) from that certificate,

    +
  4. +
  5. +

    decide which signing authority to use,

    +
  6. +
  7. +

    send the CSR to that signing authority, with your credit card details and proof that you own +itsabritishthing.com,

    +
  8. +
  9. +

    wait for them to send you your new certificate, then

    +
  10. +
  11. +

    install it on your web server.

    +
  12. +
+
+
+

That is exactly what you will need to do for Command Centre. Start with +the DNS alias for your server instead of www.itsabritishthing.com. The +next few steps are a Googling exercise for the reader because they +depend on which authority you choose. Finish with a simple process +covered in the section called "Replacing the web service certificate" +of the Configuration Client’s user guide. Briefly, it is:

+
+
+
    +
  1. +

    Go to Server properties → Web Services → Manage Certificates in the REST API section,

    +
  2. +
  3. +

    change the radio box to Custom Certificate,

    +
  4. +
  5. +

    click Import, and

    +
  6. +
  7. +

    browse to your certificate file.

    +
  8. +
+
+
+

At time of writing, Gallagher Group has no association with +www.itsabritishthing.com.

+
+
+
+
+
+

Other Command Centre items

+
+
+

Version 8.00 added fence zones, access zones, alarm zones, doors, +macros, and outputs (relays and LEDs) under two licences. The RESTStatus +licence lets you see their status and some basic configuration, and the +RESTOverrides licence lets you send overrides to them. So you can open +doors, run macros, disarm fence and alarm zones, toggle outputs, and so +on. Version 8.10 added inputs, which are often connected to physical +devices such as reed switches and infrared sensors. Version 8.30 added a +method /api/items/updates which lets you monitor several items with +one connection. Version 8.50 added operator groups, schedules, and day +categories.

+
+
+

Note that the REST API does not let you create, edit, or delete any of +these items except schedules. The configuration client is still the +place for that.

+
+
+

Each of those item types has its own controller, its own block of links +in /api, and its own section in the developer documentation. +Schedules, day categories, and the items with status like zones and +hardware have their documentation under "Status and Overrides". +Because the items/updates method is on the items controller it in with +Alarms and Events. Operator groups are in with cardholders.

+
+
+

As an example of how to use these APIs, here is how you would list all +your doors and get the link to open one of them:

+
+
+
+
GET /api
+// You would find and use the URL at features.doors.doors.href, which in 8.30 is:
+GET /api/doors
+// To search for one door use the 'name' parameter:
+GET /api/doors?name="Greendoor"
+// But if all you intend to do is open it, you only need the override links:
+GET /api/doors?name="Greendoor"&fields=commands
+
+
+
+

Overriding items

+
+

To send an override to an item you make an HTTP POST to a URL that you +get from the item itself.

+
+
+

The output from the last example above, that requested the commands +block of the door called 'Greendoor', is:

+
+
+
+
"results": [
+  {
+    "commands": {
+      "open": {
+        "href": "https://localhost:8904/api/doors/507/open"
+      },
+      "free": {
+        "href": "https://localhost:8904/api/access_zones/533/free"
+      },
+      "freeUntil": {
+        "href": "https://localhost:8904/api/access_zones/533/free"
+      },
+      "freePin": {
+        "href": "https://localhost:8904/api/access_zones/533/free_pin"
+      },
+      "freePinUntil": {
+        "href": "https://localhost:8904/api/access_zones/533/free_pin"
+      },
+      "secure": {
+        "href": "https://localhost:8904/api/access_zones/533/secure"
+      },
+      "secureUntil": {
+        "href": "https://localhost:8904/api/access_zones/533/secure"
+      },
+      // ... ten more commands omitted for brevity ...
+      "cancel": {
+        "href": "https://localhost:8904/api/access_zones/533/cancel"
+      }
+    }
+  }
+]
+
+
+
+

Normally a search would return an ID, href, and name, and it would not +return that block of commands, but we turned that on its head by using +the fields query parameter to request the commands block and nothing +else. Each of the objects inside it is a named command containing an +href which, when you POST to it, sends an override to the item. For +example, if that was a door on your system and you pasted the URL from +commands.open into Postman, and POSTed it, the unlock relay on that +door would fire.

+
+
+

Each item type has a different set of commands you can send it. They +vary in type and number: outputs have four and access zones have 21. +Most of those access zone overrides are also available on the zone’s +doors, for convenience.

+
+
+

Overrides don’t need anything in the body of the POST, but those with +'Until' in the name of the command will use a timestamp if you send it:

+
+
+
+
// POST /api/access_zones/533/free
+{
+  "endTime": "2020-03-06T00:00:00Z"
+}
+
+
+
+

That example would put the door’s entry access zone in free mode until +midnight March 6.

+
+
+
+

Status flags

+
+

Just as each item type has its own commands, each also has its own set +of status flags. A door can be open or closed, for example, while an +access zone can be secure or free. Each also has its own set of flag +rules that they will always follow. Doors, inputs, and outputs are quite +simple but fence zones have half a dozen rules thanks to the voltages +they deal with.

+
+
+

The developer documentation clearly lays out all the status flags items +can return, and their rules. For example, here is part of the section on +outputs:

+
+
+
+
+

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 means the output is under the effect of an override.

    +
  • +
+
+
+

If and only if the output is online, one of relayStateUnknown, +closed, or open will appear. Of the above, only overridden can +appear when the output is offline.

+
+
+
+
+

The above tells you that the first flags you should look for are +relayStateUnknown, closed, and open. If none of those is in the +flag set then your output is offline. Other flags will tell you what the +problem is, if you want to go deeper, but it is probably enough for your +integration to know that the output’s state is uncertain and it should +subscribe to updates in case that changes.

+
+
+

At last count there were eleven status flags common to all items. Some +are not so serious, like the flags that indicate the item is shunted +(muted) or is not fully configured yet. Others indicate an actual +problem like a network outage, a cable fault, or a service not running. +The developer documentation covers them all (search for 'abnormal +status').

+
+
+

There is a lot more on the topic in the reference documentation.

+
+
+
+

Subscribing to updates (one item at a time, pre-8.30) TODO

+
+

This section TODO. What follows is a broad outline of what the section +should contain.

+
+
+

GET the updates link on an item’s details page. It is a long poll, so +the server won’t respond until it has something for you (or it times out +after about 50s). Then stay up to date by entering a loop GETting the +next link.

+
+
+

You really should be using bulk item updates, in the section below, because this method requires one +TCP connection per monitored item. Why use this?

+
+
+
    +
  • +

    You’re not running 8.30 yet, or

    +
  • +
  • +

    you’re monitoring only one item and want a slightly simpler call, or

    +
  • +
  • +

    your client wants to wait longer than 30s between GETs (but it is hard +to imagine a client needing that).

    +
  • +
+
+
+
+

Subscribing to updates (many items, 8.30+) TODO

+
+

A rough outline: GET /api then POST a document to the link at +features.items.updates (which is /api/items/updates in 8.30, but may +change, which is why you should use the page at /api).

+
+
+

The body of your POST should look like this:

+
+
+
+
{  "itemIds": ["508", "526"]  }
+
+
+
+

Those numbers are item IDs. Even though they look like integers they +must be in quotes because in the future they could contain alphas. Place +as many in the array as you like. We tested 1,000 without it affecting +performance.

+
+
+

The POST will return with the status of all your items and a next link. +GET that link, and keep GETting it in a loop to stay up to date. The +calls will block if there are no changes to report. Sleep between calls +to avoid tight loops. But not longer than 30s, otherwise the server will +drop your session, thinking you have walked away.

+
+
+

The first GET will return the same states that the POST did, which seems +redundant, but that is just the way it is. Just keep looping.

+
+
+

This is a far better way of monitoring multiple items that the previous +because it only uses one server connection per client.

+
+
+

At time of writing (8.80) this is the only API call that maintains state +between calls. All the others require their context to arrive with the +query.

+
+
+
+

Schedules and day categories TODO

+
+

Schedules arrived in 8.40. The API lets you view, create, and delete them, and +modify how they affect their items. It does not let you change the items +they affect.

+
+
+ + + + + +
+
Tip
+
+
+

If you are not familiar with Command Centre:

+
+
+

There are seven types of schedule. They all contain a list of day +categories (more on those later), and for each day category, a list of +times (00:00 through 23:59) paired with an action to perform at that time.

+
+
+

Where the schedule types differ is in the item types they can control, and +the actions that they trigger on the item they are controlling.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Schedule type

Actions

Cardholder

Access granted / denied

Access zone

Zone mode (secure, free, etc.).

Alarm zone

Zone mode (armed, disarmed, etc.).

Output

On / off / cancel overrides

Notifications

On / off

HV/LF

High / low / cancel overrides

Elevator kiosk

Control mode

+
+

The access zone and alarm zone schedules can switch their zone to any mode. +The other five select between two modes.

+
+
+

Some of them have another action that cancels any untimed overrides that +happened since the last scheduled changed, returning control back to the +schedule. It won’t affect overrides that have an end time.

+
+
+
+
+

When updating a schedule you must replace its entire list of day +categories and times. This is quite different from how you update other +lists via this API. The JSON schema has room for us to accept the normal +style as a future enhancement, but in 8.40 you must replace all a +schedule’s schedules at the same time. That suits our target use-case, +which is for an integration to download the schedule, de-serialise it +into an object model, change some part of it, then re-serialise and +upload it back.

+
+
+

As of 8.80, the only types of item that the API can create are +cardholders, visits, and schedules.

+
+
+
+
+
+

Visitor management

+
+
+

Introduction

+
+

The Command Centre Visitor Management feature allows you to manage visit items. A visit contains

+
+
+
    +
  • +

    a list of expected visitor cardholders,

    +
  • +
  • +

    a host cardholder, responsible for a visitor while on site,

    +
  • +
  • +

    a reception, which is a location at which visitors may arrive,

    +
  • +
  • +

    a visitor type, that serves two purposes: an access group that Command Centre will add +your visitors to when you add them to the visit, and an index into more visitor management +configuration,

    +
  • +
  • +

    a list of visitor access groups that your visitors need to be in while on site,

    +
  • +
  • +

    other flat fields such as the dates of the visit and a description.

    +
  • +
+
+
+

The host is person who Command Centre should notify when a visitor +signs in. If a visitor does not have a card, tag boards and reports will +show them in the same access zone as their host. A cardless visitor +appears to follow his or her host around the site, in other words.

+
+
+

The visitor type (an access group) is there so that visitors can have +PDF values assigned before they arrive. This is useful for aids to +identification such as photos and driver’s licence or passport numbers. +Recall that in order to hold a value for a PDF, a cardholder must be in +one of the PDF’s access groups.

+
+
+

The visitor access groups (not the visitor type access group) are +there so that after a visitor signs in they can open doors using a card. +No access groups: no access.

+
+
+
+

Capabilities of the API

+
+

The API lets you read the relevant parts of visitor management +configuration, list receptions, and CRUD visit items, including their +visitors.

+
+
+

It does not let you change visitor management configuration. You do +that on divisions in the Configuration Client.

+
+
+

v8.90 lets you sign visitors in or out and mark them on or off site.

+
+
+
+

Setting up Command Centre to use Visitor Management via the API

+
+

To use the Visitor Management feature, you must:

+
+
+
    +
  • +

    add some visitor management configuration to a division in the Configuration Client, including at +least one visitor type (access group) with at least one host type group and visitor access +group, and

    +
  • +
  • +

    create at least one reception item in the same division, again in the Configuration Client.

    +
  • +
+
+
+

Your software can then use the API to get that division configuration +and the list of receptions. When it creates a visit it will need to +comply to the rules in the division configuration, including:

+
+
+
    +
  • +

    the visit’s visitor type access group must be in the same division as the visit’s reception,

    +
  • +
  • +

    the visit’s visitor type must be one of the division’s visitor types,

    +
  • +
  • +

    the visit’s host must be a member of at least one of that visitor type’s host access groups,

    +
  • +
  • +

    the visit’s visitor access groups must be a subset of that visitor type’s visitor access groups.

    +
  • +
+
+
+

The division in question is the visit’s reception’s division.

+
+
+

Command Centre reevaluates its rules every time you change a visit. We +aimed to provide useful error messages, so if you receive anything +except a 200-level response code please look in the body.

+
+
+

When you add a visitor to a visit, Command Centre will add that +cardholder to the visit’s visitor type access group. Then you can add +PDFs to the cardholder (such as an image). When the cardholder signs in +using a Gallagher Visitor Management Kiosk, or when a greeter signs them +in using the Visitor Management Client, Command Centre will add the +visitor to the visit’s visitor access groups.

+
+
+
+
+
+

The API gateway

+
+
+

The API gateway lets clients connect to the server when they cannot reach its port 8904 directly. +Command Centre needs to be connected to a cloud server, and it needs to turn on the API gateway +globally and in each REST Client item that needs it (which only takes a few clicks) then the client +simply changes the host and port in its connection URL to the cloud server that the Command Centre +server is connected to. That is most likely to be one of these two:

+
+ ++++ + + + + + + + + + + +

Australia

commandcentre-api-au.security.gallagher.cloud

United States

commandcentre-api-us.security.gallagher.cloud

+
+

The port (8904, unless you changed it from the factory setting) also has to go because the gateway +listens on the default port for HTTPS, 443.

+
+
+

Everything else is the same, including the API key, the client certificate, the root path (/api), +and the documentation URL https://gallaghersecurity.github.io.

+
+
+

The request’s source IP number will not be that of your client host by the time the request +arrives at our gateway. It will be the internal address of one of Amazon’s load balancers, so you +will need to untick 'Enable IP Filtering' when you move from the Command Centre server to the API +gateway.

+
+
+

There are no additional licence requirements for the API gateway.

+
+
+

Note that due to the traffic hairpinning through Amazon’s servers, throughput is significantly +reduced.

+
+
+

For a lot more detail about the API gateway, see its technical information paper at +https://gallaghersecurity.github.io/docs/Command%20Centre%20Cloud%20Api%20Gateway%20TIP.pdf

+
+
+
+
+

Notes for penetration testers

+
+
+

This section contains answers to questions raised after penetration tests.

+
+
+

Also see The request process for how the server authenticates and authorises requests.

+
+
+

Item IDs are easily predicted

+
+

All Command Centre’s items are referenced by a URL containing the item’s database ID, which is a +small integer. It is trivially easy to generate the URL ("href") of every item in the system by +starting at ID number one and iterating up. Some tests flag this as an issue.

+
+
+

We expect attackers to do this. We do not rely on a client’s ignorance of server state to protect +it (other than API keys, of course). The server will always enforce the licensing and privilege +models. For example, when a client asks for the details of an item it is unable to view the server +will respond as though the item does not exist (with a 404).

+
+
+

The external penetration testers Gallagher engages have access to our source code so that they can +conduct white-box testing and fully exercise the privilege model. Their reports are available on +request.

+
+
+
+

There is no rate-limiting of authorised requests

+
+

The API does not throttle correctly-authenticated API requests, which makes it look like it is not +protecting itself from denial-of-service attacks.

+
+
+

It protects itself from incorrectly-authenticatd API requests. It raises medium-priority alarms for +the first few bad requests, then another at the highest priority to inform operators that it is +under a DoS, after which it falls silent until the attack ends.

+
+
+

We are looking at options for rate-limiting the API, but it is a balance between protecting +the server from an unlikely situation and preserving the normal operation of our integrations, some +of which make a huge number of requests per second.

+
+
+

We consider a DoS attack from a fully-authenticated bad actor (in other words, one with a valid API +key, TLS certificate, and source IP) is not likely enough to justify urgency.

+
+
+

So far, the denials of service our customers have observed were caused by authenticated integrations +acting improperly. To help developers avoid that the reference documentation contains sections +headed "efficiency tips".

+
+
+
+

The server uses the Host header

+
+

Our web server uses the Host header sent by the client to form URLs that it sends back. Although +the server does not change its behaviour based on the header’s value, using it to build strings is +enough to cause a negative result on some tests.

+
+
+

A client sending a malformed Host header will only affect itself. This would be a problem if an +attacker was modifying (poisoning) a valid request on its way to us, but if the attacker could do +that it would have observed a valid API key and TLS certificate, plus it would be able to pollute +our responses on their way back to the client, so it would hardly bother with this attack.

+
+
+

This class of vulnerability is a problem when there is an HTTP cache between client and server. +This is never the case for program interfaces.

+
+
+
+
+
+

Monitoring the API

+
+
+

The web server presents its vital statistics in Windows performance counters, and (optionally) +writes every API call to a log file.

+
+
+

Performance Counters

+
+

Every API call increments a Windows Performance counter called 'REST API Request Rate'. You can +find it, along with other performance counters updated by Command Centre’s other API servers, in a +category called 'Gallagher Command Centre'. That counter will give you a view of how many calls the +API is receiving.

+
+
+

Per-client performance counters

+
+

If you are running 8.90 or later, another category called 'Gallagher Command Centre REST clients' +contains counters that will show you which of your clients are hitting the API the hardest. They +are called 'Requests per second' and 'Total processing time (ms)' each with an instance for each +REST Client item in Command Centre.

+
+
+ + + + + +
+
Tip
+
+An instance of a performance counter is one created at run-time to suit to the environment. +They are the lowest tier in the hierarchy: category, counter, instance. +
+
+
+

In our case, the environment is a pool of REST clients so CC creates an instance for the REST Client +item the first time an API call arrives for that item (i.e., the first time an API call arrives with +that item’s API key in its Authorization header).

+
+
+ + + + + +
+
Note
+
+The instance takes the name that the client item had when the session was first created, which +is also when all its privileges were calculated. If you rename a REST Client item and notice that +no new performance counter appears, that’s why. Click on the 'Refresh Operator privileges' button +in the item’s configuration page if you need it to update smartly. +
+
+
+

Also note that some characters are not permitted in the name of a performance counter, so the server +may have adjusted them to suit.

+
+
+
+

Per-route performance counters

+
+

The 'Gallagher Command Centre API routes' category, also added to 8.90, will show you which API +routes (a.k.a. endpoints) are the most popular. It contains counters called 'Requests per second' +and 'Total processing time (ms)' again but this time each has one instance for each API route.

+
+
+

Only routes that a client has called will have a counter.

+
+
+

Like client names, API routes have their URLs heavily modified to become suitable performance +counter names and to make them more readable.

+
+
+
+

A word of warning about processing times

+
+

The processing time that comes out of these counters for an API call does not necessarily reflect +the CPU or I/O cost of that call. It is simply the difference in wall-clock times before and after. +If the operation the client requested had to wait for a resource, such as a lock, then the call will +look expensive even though it spent part of its time in a queue.

+
+
+

Therefore these processing times only represent the true cost of an operation when the server and +database were able to give it their full attention.

+
+
+
+
+

Access log

+
+

Servers 8.90 and later write a line to an access log for every API request.

+
+
+

It is controlled by a DWORD value called LogRESTRequests inside the registry key +HKLM\SOFTWARE\WOW6432Node\Gallagher\Command Centre, set to one by the installer. Change its value +to zero to stop the logging. You do not need to restart Command Centre.

+
+
+

9.00 servers and later truncate URLs if they are too long. This is controlled by a DWORD in the +registry called LogRESTURLMaxLength: if a URL has more characters in it than that, it will be +truncated. The default is 200. Zero suppresses URLs altogether. If you want your URLs to go into +the log unmolested, set it to a high value (such as four billion).

+
+
+

The default file name is RESTAccess.log and the file will appear in the same folder as the +server’s licence and other logs. NLog will rotate it once per day by adding a time stamp to its +name. After ten days it will delete it. There is no limit to the size of the file. Those settings +are in the server’s application config, but we advise against changing them as doing so may affect +our ability to offer technical support. Also, your changes may not survive an upgrade. Also, a +syntax error in the app config will prevent Command Centre from starting.

+
+
+

The default file format is JSON, for easy consumption by the popular log aggregation platforms. +Each request will be on one line. Here is an example busted out and indented for easier reading:

+
+
+
+
{
+  "request_time": "2022-12-02T03:41:52.5732150Z",
+  "ip":           "127.0.0.1",
+  "client":       "REST Client item name",
+  "method":       "GET",
+  "url":          "/api/events?top=9999",
+  "status":       200,
+  "elapsed_ms":   502,
+  "endpoint":     "api/events"
+}
+
+
+
+

request_time was the time when the API call arrived, but the log line goes to disk when the call +finishes, so it will not always be an increasing value. A long poll, for example, may be logged 50 +seconds after it was requested, by which time many other (quicker) calls may have started, finished, +and been logged.

+
+
+

endpoint will be null (missing, actually) if the web server couldn’t find a route to service the +request. The status of that will be a 404. Somebody didn’t read about the importance of /api.

+
+
+

The format of this file, plus its name and rotation policy, are yours to configure via the +application config file. There is an example in there of how to produce a format closer to the +Apache common log format. However:

+
+
+ + + + + +
+
Caution
+
+Contact Gallagher Technical Support with your wishes before editing Command Centre’s +configuration file. It is large and complex and if you get it wrong, the server will not start. +Worse, it may start but then not behave as you expect. +
+
+
+
+
+
+

Appendix: Privilege table

+
+
+

This is not the complete list of privileges! See the topic 'Which Operator Privileges +you require' in the Configuration Client’s online help for more.

+
+
+

Remember that privileges lie on divisions, not on items, so when this +table says you need a privilege on some item, take it to mean that you +need that privilege on the division containing that item, or (because +every division inherits the privileges of its parent) one of that +division’s ancestors.

+
+
+

'Advanced User' grants nearly all privileges, so I don’t mention it here.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GoalPrivileges required

View cardholder data at /api/cardholders and /api/cardholders/id except notes and operator +fields.

'View Cardholder' or any of the privileges that allow editing a cardholder, on the cardholder’s +division.

View all cardholder data

'View Cardholder' or any of the cardholder editing privileges on the cardholder’s division, plus +'View Cardholder Notes' reveals notes, and 'View Lockers and Assignments' adds locker detail.

View cardholder PDF values

'View Cardholder' or any of the cardholder editing privileges on the cardholder’s division, plus +your operator group needs to give you 'view' or 'edit' access to the PDF, or the PDF’s default +privilege must be 'view' or 'edit'.

Create cardholders, but not modify them.

'Create Cardholders' on the cardholder’s division.

Create and edit cardholders, except their notes and operator settings.

'Create and Edit Cardholders' on the cardholder’s division.

Edit cardholders, except their notes and operator settings.

'Edit Cardholders' on the cardholder’s division.

Edit cardholder notes.

One of the privileges that lets you edit cardholders as well as either of 'Add Cardholder Notes' or +'Edit Cardholder Notes' on the cardholder’s division. It is different in the thick clients: there, +one of the last two is enough.

Modify cardholder access group memberships.

One of the privileges that lets you edit cardholders on the cardholder’s division plus 'Modify +Access Control' on the group’s division. 'Modify Access Control' on the group’s division is enough +in the thick clients.

Change a cardholder’s location.

'View Cardholder' on the cardholder, and 'Manage Cardholder Location' on the target access zone’s +division, when you are moving the cardholder into an access zone, otherwise any division, when you +are moving the cardholder outside the system. By the way: collecting access zones normally requires +the RESTStatus licence, but there is a variant of that call that returns just the zones your +operator is allowed to move cardholders to that only requires the RESTCardholders licence.

Assign a card to a cardholder.

One of the privileges that lets you edit cardholders ('Create' or 'Create and Edit') on the +cardholder’s division and on the card type’s division.

View assignable card types at /api/card_types/assign

One of the 'Edit Cardholder' privileges on the card type’s division.

View card types at '/api/card_types'

'View Site' or 'Configure Site' on the card type’s division. The privileges that let you create or +edit cardholders also reveal PIV types.

Change locker assignments.

A privilege that lets you edit cardholders on the cardholder’s division plus 'Manage +Locker Assignments' on the locker’s division. 'Manage Locker Assignments' on the locker’s division +is enough in the thick clients.

Disable a card.

One of the privileges that lets you edit cardholders on the cardholder’s division. The 'Disable +Card' privilege has no effect on current versions of the REST API. In the thick clients you do not +need edit privileges on the cardholder if you have 'Disable Card'.

Change a card’s PIN.

"Allow re-printing and re-encoding" or "Print/preview & encode card" or "Edit cardholders" or one +of the privileges that let you edit a cardholder (which in 8.90 are "Edit cardholders" and "create and edit cardholders").

De-authorise a cardholder.

'De-authorise Cardholder' or one of the privileges that lets you edit cardholders on the +cardholder’s division. There is a bug in versions up to and including 8.70 that means you need +'Edit Cardholders' to de-authorise a cardholder. 'De-authorise Cardholder' works on its own in 8.80 +and later.

Delete a cardholder.

'Delete cardholders' on the cardholder’s division. Not 'Create Cardholders', 'Create and Edit + Cardholder', or 'Edit Cardholder'.

Redact a cardholder’s events.

'Delete Cardholder History' on the cardholder’s division.

Redact a cardholder.

'Delete Cardholder History' and 'Delete Cardholders' on the cardholder’s division.

Edit a relationship between cardholders.

One of the privileges that lets you edit cardholders on the cardholder’s division and on the role’s +division.

View a cardholder’s operator configuration

'View Operators' or one of the privileges that let you modify an operator, such as 'Edit Operators' +or 'Enable/Disable Operator'.

View operator groups

'Edit Operator Groups' or 'View Operator Groups'.

Change a cardholder’s operator groups

'Modify Operator Group Membership'.

View PDF definitions at /api/personal_data_fields

'View…' or 'Edit Personal Data Definitions' on the PDF’s division.

View events at /api/events

'View Events and Alarms' or any of the privileges that allow processing alarms, on the division of +the event or alarm.
+Or (in 8.70) 'View Cardholder Events' on the division of the cardholder related to the event and on +the event’s division.

Acknowledge, process, or mark alarms as viewed.

'Edit Alarms' on division of the event or alarm.

Create new events (8.10+)

'Create Events and Alarms' on the division of the source of the event, if the source is not your + REST Client item, or any division otherwise. Prior to 8.90 having it on any division was enough + (bug).

List access groups.

'View Access Groups' or 'Edit Access Groups' on the access group’s division.

List competencies.

'View Site' or 'Edit Site'.

Receive schedule hrefs in an access group

'View Schedules' on the schedule’s division. 'View Site', 'Configure Site', and 'Edit Site' will +not do it.

List access zones and receive their hrefs in other results

'Edit Site', 'View Site', or 'Override' on the access zone’s division.

Override an access zone’s mode.

'Override' on the zone’s division.

List alarm zones

'Edit site', 'View Site', or 'Override' on the alarm zone’s division.

List doors

'Edit site', 'View Site', or 'Override - Open Door' on the door’s division.

Override doors

'Override - Open Door' on the door’s division.

List fence zones

'Edit site', 'View Site', or 'Maintenance Override' on the fence zone’s division.

Override fence zones

'Maintenance override' on the fence zone’s division.

List inputs

'Edit Site', 'View Site', 'Maintenance Override' on the input’s division.

List macros

'View Site', 'Run Macros', or 'Schedule and Run Macros' on the macro’s division.

List outputs

'Edit site', 'View site', or 'Override' on the output’s division.

List day categories

'Configure Site', 'Edit Schedules', 'View Site'. Day categories are divisionless, so having one of +those privs on any division is enough. 'View Schedules' is true to its word: it will not show you +day categories.

List schedules

'View Schedules', 'Edit Schedules', 'Schedule Access Zone' (though the last one only gives you +access to access zone schedules, not the other five types).

Create, edit, and delete schedules.

'Edit Schedules'.

List elevator groups

'Edit Passenger Details' is probably the one you want. 'View Site' lets you see elevator groups, but +might not let you use them on a cardholder.

Set a cardholder’s default floors (for calling elevators)

'Edit Passenger Details'.

Run a macro at '/api/macros/id/run'

'Run Macros' or 'Schedule & Run Macros' on the macro’s division.

Shunt or unshunt an item.

'Maintenance Override' on the item’s division. 'Override', which is good for most other overrides, +is not enough to shunt or unshunt an item.

View a division’s visitor management configuration, and view receptions

'View Site', 'Edit Site', 'View Visits', 'Edit Visits', or 'Manage Receptions'.

View a visit

'View' or 'Edit Visits'. 'Manage Receptions' will not do it.

Modify a visit

'Edit Visits'. 'Manage Receptions' will not do this either.

Modify a division

'Configure Site' or 'Edit Enterprise Data Interfaces' on the division itself.

+
+
+
+

Appendix: Features and licences

+
+
+

Alarms and events

+
+

7.80 allows reading and writing unprocessed alarms. Clients can read all +their fields, mark them as viewed, add comments, acknowledge, and +ultimately process them.

+
+
+

7.80 allows reading events. Clients can see all fields, including these +related items:

+
+
+
    +
  • +

    cardholder, entry/exit zone, division,

    +
  • +
  • +

    (in 8.00) the source item, and the operator and access group on +head-end events (i.e., those that did not come from a controller),

    +
  • +
  • +

    (in 8.30) the door on guard tour events, and

    +
  • +
  • +

    (in 8.40) the item that an operator modified.

    +
  • +
+
+
+

At time of writing (8.30), the following are absent from the list of items related to an +event: locker bank and locker, door (unless it is a guard tour event), +missing competency, car park, and car park space. In practice that is +rarely a problem since those items are often the event’s source, and +will therefore be in the source block.

+
+
+

You need RESTEvents in your licence for all the above.

+
+
+

8.10 allows creating external events with the RESTCreateEvents licence. +Clients can use the usual item types as the source of the event and can +attach one of each of these as related items:

+
+
+
    +
  • +

    cardholders,

    +
  • +
  • +

    operators,

    +
  • +
  • +

    entry access zones,

    +
  • +
  • +

    access groups,

    +
  • +
  • +

    lockers and locker banks, and

    +
  • +
  • +

    doors.

    +
  • +
+
+
+
+

Cardholders and supporting items

+
+

7.90 allows most administrative functions on cardholders, including full +credential maintenance.

+
+
+

It also gives read-only access to supporting items:

+
+
+
    +
  • +

    access groups,

    +
  • +
  • +

    competencies,

    +
  • +
  • +

    card types,

    +
  • +
  • +

    roles,

    +
  • +
  • +

    lockers,

    +
  • +
  • +

    (in 8.10) PDF definitions,

    +
  • +
  • +

    (in 8.50) default elevator floors, and operator privileges.

    +
  • +
+
+
+

8.20 allows moving cardholders between access zones. To support that it +added a call to the access zones controller that requires the +cardholders licence, not RESTStatus as the other access zone calls do.

+
+
+

8.30 allows subscribing to cardholder changes, for integrations that use +Command Centre as a source of users.

+
+
+

8.40 shows an access group’s access zones, Salto items, and privileges +(there are 20). In the Access Group window in the Configuration Client +these are the 'Access', 'Salto Access', and 'Privileges' tabs. These are +all read-only fields.

+
+
+

8.50 adds views of operator groups, receptions, and visitor management +settings, and read-write access to visits and cardholders’ operator and +elevator settings.

+
+
+

8.80 adds cardholder redaction: scrubbing a person’s activity and identifying information out of +the database to help with privacy protection regulations.

+
+
+

Car parks remain on the roadmap.

+
+
+

These cardholder functions require the RESTCardholders licence. 8.20 +added lockers and locker banks to the RESTStatus licence as well, minus +the cardholder information.

+
+
+
+

Non-cardholder items

+
+

8.00 allows read access to basic configuration, status, and all +overrides, to:

+
+
+
    +
  • +

    access zones, alarm zones, and fence zones,

    +
  • +
  • +

    doors,

    +
  • +
  • +

    macros, and

    +
  • +
  • +

    outputs and (in 8.10) inputs.

    +
  • +
+
+
+

You will need the RESTStatus licence for the GETs and RESTOverrides for +the override POSTs. 8.60 added the GETs to RESTOverrides, but in a +slightly limited way: they will search for items to override, but they +cannot return their status.

+
+
+

8.20 added lockers, previously only visible with the RESTCardholders +licence, to the RESTStatus licence.

+
+
+

8.30 added the ability to monitor more than one item per connection.

+
+
+

8.50 allows read access to day categories and read-write access to +schedules. You can manage the day categories and times on a schedule, +but not which items it affects.

+
+
+

Day categories are divisionless, which led to a slight change in the +/items controller: divisionless items did not appear there before +8.50; now they do (subject to privilege checks, of course).

+
+
+

8.50 allows viewing elevator groups.

+
+
+

As of 8.60, cardholders, schedules, and visits are the only items you +can modify via the API.

+
+
+
+
+
+ + + \ No newline at end of file