Rest Structure

Rest API structure

This section describes the Keep by Feenics™ Application Program Interface from the vantage point of the HTTP protocol. This section will be of particular interest to non .NET developers, for example those using PHP, Python, Node or JavaScript.

Authentication

All calls to the API must be authenticated. The exception to this rule is a POST to the authentication endpoint https://keepapi.feenicshosting.com/token. A successful call to this endpoint will provide an access_token that can used for all other API calls.

The sample python script illustrates making such a call. This script imports the requests library and the built-in python package called json to make working with JSON data easier.

A successful response (HTTP Status 200) will yield the illustrated JSON object. Take note that the access token is set to expire, in this example in 86399 seconds (1 day minus 1 second). You must claim a new access token within that time period using the supplied refresh token.

Example in PYTHON

import requests
import json

r = requests.post('https://keepapi.feenicshosting.com/token',
    data = {
        'grant_type':'password',
        'client_id':'consoleApp',
        'client_secret':'consoleSecret',
        'username':'INSTANCENAME\\USERNAME',
        'password':'PASSWORD',
        'instance':'INSTANCENAME'}
)

result = json.loads(r.content)
access_token = result[u'access_token']
print(json.dumps(result,indent=4))

{
    "access_token": "PBSWlquRR4nXpDvxLqXyCFXDdPAiRib5xasIFAHf14d6wMwVtCvJzo7Mmu14jxR3oH5M8tvMbo0W8MDQL0iRAwtpGhLJ7msqCeTFsbrOgtLkcEu8of_bzOl1ccfE-zsPNv_fcCe9izedtZWe5mztk_hRMDDXPW50QYNgbDEqpURxEHYRtjcYiBgGiLRmiYbQpwmWK2SwuZFtrQnbvOl-o9Hs2qk9hVB1eMZ3wl1ohOsOlDa6knEy0S1RJV_BVlD28hD-zF_lrYa-jXilZsCHCv2nRaADwqZ0g3OhmZT-WHSIfhEEICzh1kZj4ohy_rOQswxi1R_yORyJf2Rkjw7ItQ",
    "token_type": "bearer",
    "expires_in": 86399,
    "refresh_token": "5bd30016a3ac160cf88835f3",
    "as:client_id": "consoleApp",
    "userName": "rgsc\\ralph",
    "instance": "5bcde6c5a3ac1600485092a0",
    ".issued": "Fri, 26 Oct 2018 11:52:53 GMT",
    ".expires": "Sat, 27 Oct 2018 11:52:53 GMT"
}

To claim a new access token POST to the authentication endpoint a refresh request object similar to:

Example in PYTHON

{
        'refresh_token':'5bd30016a3ac160cf88835f3',
        'grant_type':'refresh_token',
        'client_id':'consoleApp',
        'client_secret':'consoleSecret'
}

If the POST is successful (HTTP Status 200) you will receive a new access token value and new refresh token. Repeat this process within the timespan of the expire value to avoid having to prompt the user for login credentials again.

Please note the expire period may chance from 24 hours to a shorter time span. Use the supplied value in the authentication response to determine an appropriate refresh interval.

Example in PYTHON

headers = {'Authorization': 'bearer %s' % access_token}
currentInstance = requests.get('https://keepapi.feenicshosting.com/api', headers=headers)
print(json.dumps(json.loads(currentInstance.content), indent=4))

{
    "$type": "Feenics.Keep.WebApi.Model.InstanceInfo, Feenics.Keep.WebApi.Model",
    "FullyQualifiedDomainName": "rgsc",
    "PasswordPolicy": null,
    "Key": "5bcde6c5a3ac1600485092a0",
    "CommonName": "Really Good Software Company",
    "InFolderHref": "/api/f/5af2edd1a3ac160cb0c20901",
    "InFolderKey": "5af2edd1a3ac160cb0c20901",
    "Links": [
        {
            "$type": "Feenics.Keep.WebApi.Model.Link, Feenics.Keep.WebApi.Model",
            "Relation": "Settings",
            "Anchor": {
                "$type": "Feenics.Keep.WebApi.Model.Anchor, Feenics.Keep.WebApi.Model",
                "Href": "settings",
                "Text": "Settings"
            }
        },
... etc ...
        {
            "$type": "Feenics.Keep.WebApi.Model.Link, Feenics.Keep.WebApi.Model",
            "Relation": "People",
            "Anchor": {
                "$type": "Feenics.Keep.WebApi.Model.Anchor, Feenics.Keep.WebApi.Model",
                "Href": "people",
                "Text": "People"
            }
        },
... etc ...
    "Notes": [],
    "Tags": [],
    "Monikers": [],
    "Href": "/api/f/5bcde6c5a3ac1600485092a0"
}

Following the Restful pattern the API is designed to be navigated by code, much the same as a web site is navigated by human readers. Just as a web site has a starting point default document, so to does the Keep By Feenics™ API. https://keepapi.feenicshosting.com/api

You must supply the access_token from the authentication request for this and all other API calls. This is supplied as an Authentication header with the bearer scheme. Notice that the you are supplying any parameters to this base end point. The InstanceInfo object that is returned was identified from the content of the access_token.

What must be observed by the non .NET developer is that the Keep by Feenics™ object model is strongly typed. Since Javascript Object Notation (JSON) does not support a type declarations we follow the convention of naming the .NET datatype using the $type key and suppling the two part type identifier: Feenics.Keep.WebApi.Model.{typename} Feenics.Keep.WebApi.Model It it critical that all JSON objects supplied to the server supply the appropriate type declaration of the request will be rejected.

As descried in the BaseInfo specification all objects carry a common set of properties. For the purpose of API navigation the most important of these is the Href property and Links array.

the API consumer can construct new Resource URI values from the Href of the object in question and one of the href property of one of the Links.

For example, after getting the current instance by calling the base API endpoint we see that the relative path Href for the root instance is: /api/f/5bcde6c5a3ac1600485092a0 and that there is a People resource available.

We can construct the URL for accessing People resources from this information.

  • Search for the Link object with the Relation of People.
  • Append the Anchor.Href value to the Href of the InstanceInfo for /api/f/5bcde6c5a3ac1600485092a0/people

We can now issue GET and POST requests on this resource URL. A GET request will return a set of PersonInfo objects.

We can also issue a POST request to this resource URL. A POST request expects a new PersonInfo object in valid JSON format as the request body.

Every object that is returned from the API will have an HREF property. You can issue a PUT request to this relative path HRef to update an existing object. You can issue a DELETE request to this HRef to delete the object.

Expected HTTP Verbs

Verb Outcome
GET Returns an object of a specific object type to the caller
POST Expects a specific object type in the message body to be added to the system
PUT Updates a specific instance of an object in the system with new values
DELETE Deletes a specific instance of an object from the system

Strong typing

Keep by Feenics™ is a strongly typed API. This means that every object sent to the system or retrieved from the system will carry type information. For example a PersonInfo object retrieved from the system will have the following properties. All objects are in the Feenics.Keep.WebApi.Model namespace, and from the .NET assembly of the same name. Therefore the 3 part name of any given object type can be constructed as: Feenics.Keep.WebApi.Model + . + TypeName + , + Feenics.Keep.WebApi.Model.

For example the $type value for an InstanceInfo object is Feenics.Keep.WebApi.Model.InstanceInfo, Feenics.Keep.WebApi.Model

More complex object types, such as the PersonInfo will have imbedded types. Consider the example. Notice that the CardAssignments property is an array of CardAssignmentInfo objects, and that those object instances are also identified by the $type property.

Also notice that the Addresses property is an array of AddressInfo objects, but that each specific address is further sub-classed into MailingAddressInfo, EmailAddressInfo and PhoneInfo types.

With this strong typing in place it’s possible to POST or PUT any derived type of the AddressInfo to the addressing endpoint of the person.

Example in PYTHON

//  Sample PersonInfo Object
{
        "$type": "Feenics.Keep.WebApi.Model.PersonInfo, Feenics.Keep.WebApi.Model",
        "CommonName": "Shillington, Ralph",
        "InFolderKey": "5bcde6c5a3ac1600485092a0",
        "CardAssignments": [
                {
                "PinExempt": false,
                "ExpiresOn": "2019-10-22T15:03:58.825Z",
                "RecordId": 1,
                "$type": "Feenics.Keep.WebApi.Model.CardAssignmentInfo, Feenics.Keep.WebApi.Model",
                "IsDisabled": false,
                "OriginalUseCount": null,
                "CurrentUseCount": 0,
                "PinCode": "55566",
                "HexValue": null,
                "Note": null,
                "AntiPassbackExempt": false,
                "Href": "/api/f/5bcde6c5a3ac1600485092a0/people/5bcde6d2a3ac1600485092ed/cards/5bcde6dba3ac160048509333",
                "Key": "5bcde6dba3ac160048509333",
                "ManagerLevel": 1,
                "ActiveOn": "2018-10-22T15:03:58.825Z",
                "EncodedCardNumber": 2362,
                "ExtendedAccess": false,
                "DisplayCardNumber": "2362"
                }
         ],
        "Surname": "Shillington",
        "GivenName": "Ralph",
        "Addresses": [
                {
                "Province": "Ontario",
                "City": "Ottawa",
                "$type": "Feenics.Keep.WebApi.Model.MailingAddressInfo, Feenics.Keep.WebApi.Model",
                "IsPrivate": false,
                "Street": "302 - 2310 St. Laurent Blvd",
                "Key": "5bcde6d2a3ac1600485092e7",
                "Country": "Canada",
                "PostalCode": "K1G 5H9",
                "Href": "/api/f/5bcde6c5a3ac1600485092a0/people/5bcde6d2a3ac1600485092ed/addresses/5bcde6d2a3ac1600485092e7",
                "Type": "Work"
                },        
                {
                "MailTo": "ralph.shillington@feenics.com",
                "$type": "Feenics.Keep.WebApi.Model.EmailAddressInfo, Feenics.Keep.WebApi.Model",
                "Href": "/api/f/5bcde6c5a3ac1600485092a0/people/5bcde6d2a3ac1600485092ed/addresses/5bcde6d2a3ac1600485092e9",
                "Key": "5bcde6d2a3ac1600485092e9",
                "IsPrivate": false,
                "Type": "Work"
                },
                {
                "$type": "Feenics.Keep.WebApi.Model.PhoneInfo, Feenics.Keep.WebApi.Model",
                "Number": "16135202455",
                "Href": "/api/f/5bcde6c5a3ac1600485092a0/people/5bcde6d2a3ac1600485092ed/addresses/5bcde6d2a3ac1600485092eb",
                "Key": "5bcde6d2a3ac1600485092eb",
                "IsPrivate": false,
                "Type": "Work"
                }
        ],
         "ObjectLinks": [
                {
                "$type": "Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model",
                "CommonName": "Really Good Software Company",
                "Href": null,
                "Relation": "InstanceScope",
                "MetaDataBson": {
                        "$type": "System.Byte[], mscorlib",
                        "$value": ""
                },
                "LinkedObjectKey": "5bcde6c5a3ac1600485092a0"
                },
                {
                "$type": "Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model",
                "CommonName": "Staff Badge",
                "Href": null,
                "Relation": "BadgeType",
                "MetaDataBson": {
                        "$type": "System.Byte[], mscorlib",
                        "$value": ""
                },
                "LinkedObjectKey": "5bcde6d9a3ac16004850931d"
                },
                {
                "$type": "Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model",
                "CommonName": "Management Access",
                "Href": null,
                "Relation": "AccessLevel",
                "MetaDataBson": {
                        "$type": "System.Byte[], mscorlib",
                        "$value": ""
                },
                "LinkedObjectKey": "5bcde6dba3ac16004850932f"
                }
        ]
}

Object Relationships

The BaseInfo type has an ObjectLinks property which is an array of type ObjectLink. This is the uniform mechanism for relating objects together.

For example when a person is assigned a badge type, what is happening a new ObjectLink is being added to the person with the key of the AccessLevel being assigned. The resource URL for this relationship is constructed as follows:

Segment Description
https://keepapi.feenicshosting.com The Base address for API service
/api/f/ The identifier for instances in the system
{folderKey} the key value for the instance that contains the person
/people/ the identifier for the collection of people in the identified instance
{key} the key of value of the person that is being assigned a new access level
/connections/ the identifier for object connections for this person
{relation} the name of the relationship. For Access Levels the name must be “AccessLevel”
? the following segments are added to the URL as query parameters
relatedKey={foreignKey} the key of the foreign object - in this case the AccessLevelInfo object
isOneToOne=false a boolean value to identify if this relationship is one to one. In the case of access levels, it is not. A person may be assigned many access levels

Thus to assign an access level to a person, where the key of instance is 5bd5d96af0794c509032b7f8 and person is 5bd5d98ef0794c509032b7f9 the access level is 5bd5d98ff0794c509032b7fb then POST to the resource URL:

https://keepapi.feenicshosting.com/api/f/5bd5d96af0794c509032b7f8/people/5bd5d98ef0794c509032b7f9/connections/AccessLevel?relatedKey=5bd5d98ff0794c509032b7fb&isOneToOne=false

To remove the same AccessLevel issue a DELETE to

https://keepapi.feenicshosting.com/api/f/5bd5d96af0794c509032b7f8/people/5bd5d98ef0794c509032b7f9/connections/AccessLevel/5bd5d98ff0794c509032b7fb

Notice that in the case of a DELETE the key of the AccessLevel is a part of the URL, versus the POST where the key passed as a parameter of the Connections resource URL.

While the relationship between objects is flexible for the purpose of Card Assignments the AccessLevel relationship is mandatory.

The BadgeType relationship will associate a specific badge type to a person. However this relationship is not observed by the service. It is used by the various Client Applications to provide default values for AccessLevels and Card Expiries. In other words changing the default expiry or access levels on a badge type will not change anything for people that are assigned that badge type. The expectation is that customer that wish to force a badge type change on existing card holders will implement a script to affect that change.