Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • Host

    • Hostname of the targeted endpoint

    • Will change per-environment. eg- example.com

  • Authorization

    • Calculated signature based on the contents of the request

    • Will change per-request. eg- HMAC-SHA256 0fa36b84ab47a14ec56934927105e56d83424ec5bd649680e0d52bbb89e28ed0

  • Signed-Headers

    • Headers signed against, in addition to the request body, that are included in the signature. Note that this is case-sensitive.

    • Exactly: host,signed-headers

  • X-API-Key

    • API Key provided by Link2Feed Staff. eg- 6934927105e56d83424ec5bd64

Signing Process

  1. Gather Required Information:

    1. HTTP Method eg- GET/POST/PUT/PATCH/DELETE

    2. HTTP URL, including query parameters eg- https://example.com/api/v1/demo?id=1234

    3. Request Payload/Body

    4. Request Headers

  2. Combine and Format Data to Be Signed

    1. The data must be “escaped” for safe transit.

    2. The data is to be concatenated. Ordering of the fields need only be internally-consistent, so the data sent must match the order of the fields in the data that is signed.

    3. The package to be signed consists of three parts:

      1. A Request Line: A string consisting of the HTTP Request Line format, notably “The Request-Line begins with a method token, followed by the Request-URI and the protocol version, and ending with CRLF. The elements are separated by SP characters. No CR or LF is allowed except in the final CRLF sequence.” For example: POST /api/v1/clients/find HTTP/1.1\r\n

      2. The Signed Headers: Specifically, these are the Host and Signed-Headers headers, and their contents. Formatted as header-name, followed by a colon and a space, then the value of the header. Each header is followed by a CRLF. For example: host: redacted.link2feed.com\r\nsigned-headers: host,signed-headers\r\n

      3. The Body of the Request: If the request is JSON-formatted (ie- Content-Type is application/json) then this is simply the JSON String. If the request is formatted as form-data, each key/value pair must be a) “escaped” for safe HTTP transfer, b) split into Key and Value, c) represented as Key=Value, and d) the collection must be joined together with ampersands between key/value pairs. For example, if the form-data is firstName: Eleven, lastName: O'Clock, dob: 1980-01-01, then the body to be signed would be: firstName=Eleven&lastName=O%27Clock&dob=1980-01-01 where, notably, the ' in O'clock is escaped to %27.

    4. Combine the three parts into a single string, separated by CRLF characters. For example the above:

      Code Block
      POST /api/v1/clients/find HTTP/1.1\r\nhost: redacted.link2feed.com\r\nsigned-headers: host,signed-headers\r\n\r\nfirstName=Eleven&lastName=O%27Clock&dob=1980-01-01

      Note: As each header ends with a CRLF and then the joining adds another, there are two CRLF character blocks between the headers and the body. The same request as JSON, and with the CRLF characters rendered would be:

      Code Block
      POST /api/v1/clients/find HTTP/1.1
      host: redacted.link2feed.com
      signed-headers: host,signed-headers
      
      { "firstName":"Eleven", "lastName":"O'Clock", "dob":"1980-01-01" }
  3. Build the Signature

    1. Once the string is ready to be signed, it must go through two main processes:

      1. Gathering the HMAC SHA-256 hash of the contents, signed with the Secret Key provided by Link2Feed

      2. Converting the resultant hash to Base64

  4. Send the Request

    1. Once the Base64’ed value of the HMAC SHA-256 hash is collected, it is added as an additional “Authorization” header to the request in the form HMAC-SHA256 _______CalculatedHash_______.

    2. The request can then be sent. It must include both the HMAC signature as an Authorization header, and the API-Key as an X-API-Key header. So for example, the above JSON example request (signed against a secret key of 123456789) would produce the following request headers:

      Authorization: "HMAC-SHA256 3deOvVHhL1zyHyS7+cWPsPW+OnhxQXHJzLsR9UyAIQ4="

      Signed-Headers: "host,signed-headers"

      X-API-Key: "6934927105e56d83424ec5bd64"

      Content-Type: "application/json"

      Host: "redacted.link2feed.com"

JavaScript Example

The following is built against Postman (https://www.postman.com/) Pre-request Scripts (https://learning.postman.com/docs/writing-scripts/pre-request-scripts/). Working sample Collections for Postman are included at the bottom of this document as Appendix A.

...

Code Block
languagejs
var SECRET_KEY = pm.variables.get("SECRET");
var SIGNED_HEADERS = [
    ["host", pm.variables.get("API_URL")]
];
var SIGNED_HEADER = "signed-headers: host,signed-headers\r\n";

function getPath(url) {
    var pathRegex = /.+?\:\/\/.+?(\/.+?)(?:#|\?|$)/;
    var result = url.match(pathRegex);
    return result && result.length > 1 ? result[1] : ''; 
}
 
function getQueryString(url) {
    var arrSplit = url.split('?');
    
    if(arrSplit.length == 1) return '';
    
    var params = arrSplit[1].split('&');
    params.sort();
    
    return '?'+params.join('&');
}
 
function getAuthHeader(httpMethod, requestUrl, requestBody, requestHeaders) {
    if (httpMethod == 'GET' || !requestBody) {
        requestBody = "";
    } else {
        if(requestHeaders != null && requestHeaders["content-type"] != "application/json")
        {
            var output = "";
            Object.keys(requestBody).forEach(function(e) {
                output += escape(e) + "=" + escape(requestBody[e]) + "&";
            });
            requestBody = output.substring(0, output.length -1); // Remove the last &
        }
    }
    
    var requestLine = httpMethod +" "+getPath(requestUrl)+getQueryString(requestUrl)+" HTTP/1.1";
    
    var headers = "";
    for(var i = 0; i < SIGNED_HEADERS.length; ++i) {
        headers += (SIGNED_HEADERS[i][0] + ': ' + SIGNED_HEADERS[i][1] + '\r\n');
    }
    headers += SIGNED_HEADER;
    
    var body = requestBody;
    var hashedRequestData = [requestLine, headers, body].join('\r\n');
    var hmacDigest = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(hashedRequestData, SECRET_KEY));
    var authHeader = "HMAC-SHA256 "+hmacDigest;
    return authHeader;
}

pm.environment.set("HMAC_AUTH_HEADER", getAuthHeader(request['method'], request['url'], request['data'], request['headers']));

Additional Documentation

  1. AWS Signing and authenticating REST requests: https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html

  2. ApiAuth - HMAC authentication for Rails and HTTP Clients: https://github.com/mgomes/api_auth

  3. HMAC on Wikipedia: https://en.wikipedia.org/wiki/HMAC

  4. Online HMAC Generator: https://codebeautify.org/hmac-generator

  5. IETF RFC 2104 - HMAC: Keyed-Hashing for Message Authentication: https://tools.ietf.org/html/rfc2104

  6. HMAC Tools for Various Languages

    1. C: https://github.com/ogay/hmac

    2. Java: https://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#HmacEx

    3. JavaScript/NodeJS: https://nodejs.org/api/crypto.html#crypto_class_hmac

    4. PHP: https://www.php.net/manual/en/function.hash-hmac.php

    5. Python: https://docs.python.org/3/library/hmac.html

    6. Ruby: https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/HMAC.html

Data Types API

Purpose: This endpoint is used to gather network-specific identifiers and other configuration data. Some of it is very CNCT-specific (eg- customized text blocks for the CNCT UI) but the big value is in the data types that must be passed along as part of the required details configured in the API.

...

Purpose: This collection of endpoints are used to create and update appointments for clients.

Get Appointment Availability for

...

a Specific Agency

Purpose: This endpoint finds available appointment slots at an agency. Note: when setting up an agency to use this system, the agency can only have one appointment program available. If more than one appointment program is available, an error will prevent any availability from showing. This is an intentional design feature to simplify the experience of signing up for services at a location.

HTTP URL: https://{{ HostName }}/api/v1/agencies/{{ Agency ID }}/appointments?startDate={{ YYYY-MM-DD }}&endDate={{ YYYY-MM-DD }}&clientProfileId={{ Client ID }} where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed. {{ Agency ID }} is from one of the agencies listed in the Data Types API. The query parameters for startDate and endDate are not required – leaving them out will search for appointments for today and tomorrow. clientProfileId is optional, if included it will provide distances between the agency and the client in the response object.

HTTP Method: GET

Request Headers: Authorization, Host, Signed-Headers, X-API-Key

...

  • Sometimes-Present Fields

    • agencyId: The ID of the agency searched for.

    • agencyName: The name of the agency searched for.

    • distanceKm: The straight-line distance between the Client’s household (if their address is available) and the Agency’s address, in kilometres

    • distanceMi: The straight-line distance between the Client’s household (if their address is available) and the Agency’s address, in miles

    • timeSlots: An array of Time Slot objects each of which has:

      • dateFormat: Provides the standard date format that the system uses.

      • availableDateTime: The start date and time of the appointment, based on the agency’s local timezone.

      • availableDate: Specifically the date part of the time slot.

      • availableTime: Specifically the time part of the time slot.

      • availableRange: Both the start and end date and time of the specific time slot.

      • availabilityCount: The number of spots available at this time slot. Note: in some cases this is null, and in other cases it is zero or negative. These are all valid responses in the context of the system (null means the appointment isn’t time-based, zero means there are no available slots left, and negative means it has been over-booked, which can happen if the appointment caseload is not enforced.)

      • availableTimeslot: Fine-grained details of the date and time for the time slot.

      • locationId: The Link2Feed ID of the agency where the time slot is available.

      • location: The name of the agency where the time slot is available.

      • postcodeEligibility: A true/false response for whether or not this client is eligible, based on a postcode restriction – if this option is not present, there is no postcode restriction to consider for this program.

    • provisions: A collection of Provision options that are available with this visit.

Response Codes:

  • 200 - OK. Response payload includes the data types details.

  • 400 - Bad Request. Will respond with a message including details of the request field errors. For example: { "message": "agencyId field not supplied." }

  • 401 - Unauthorized. There was an issue with the request. Ensure that all required headers are accurate (X-API-Key, Authorization, Signed-Headers and Host).

  • 404 - Not Found. There are no available appointments during the time period of the search, for the agency in question.

  • 5XX - Server Error. Any 5XX error (500, 502, 503, 504) means a Link2Feed error has occurred. Please contact Link2Feed if this persists.

...

Code Block
[
    {
        "agencyId": 8659,
        "agencyName": "Agency 01",
        "distanceKm": 614.0448277573528,
        "distanceMi": 381.5497667107546,
        "timeSlots": [
            {
                "dateFormat": "Y-m-d H:i",
                "availableDateTime": "2021-02-08 12:15",
                "availableDate": "2021-02-08",
                "availableTime": "12:15",
                "availableRange": {
                    "start": "2021-02-08 12:15",
                    "end": "2021-02-08 12:45"
                },
                "availabilityCount": 5,
                "availableTimeslot": {
                    "year": 2021,
                    "month": 2,
                    "day": 8,
                    "hour": 12,
                    "minute": 15,
                    "dayName": "Monday"
                },
                "locationId": 8659,
                "location": "Agency 01",
                "postcodeEligibility": true
            },
            {
                "dateFormat": "Y-m-d H:i",
                "availableDateTime": "2021-02-08 12:45",
                "availableDate": "2021-02-08",
                "availableTime": "12:45",
                "availableRange": {
                    "start": "2021-02-08 12:45",
                    "end": "2021-02-08 13:15"
                },
                "availabilityCount": 5,
                "availableTimeslot": {
                    "year": 2021,
                    "month": 2,
                    "day": 8,
                    "hour": 12,
                    "minute": 45,
                    "dayName": "Monday"
                },
                "locationId": 8659,
                "location": "Agency 01",
                "postcodeEligibility": true
            },
            ...
        ],
    }
]

Book an Appointment for a Client

...

    "provisions": [
            {
                "provisionName": "Sample Food",
                "provisionId": 1509,
                "provisionDisplay": "checkbox"
            },
            ...
        ]
    }
]

Get Appointment Availability for a Whole Network

Purpose: This endpoint finds available appointment slots at all the agencies in a single network.

HTTP URL: https://{{ HostName }}/api/v1/network/{{ Network ID }}/appointments?startDate={{ YYYY-MM-DD }}&endDate={{ YYYY-MM-DD }}&clientProfileId={{ Client ID }} where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed. {{ Network ID }} is the ID of the Network organization in Link2Feed that is being queried. {{ Client ID }} is the Link2Feed ID or Link2Feed ClientProfileId (GUID) of the client interested in services. The query parameters for startDate and endDate are not required – leaving them out will search for appointments for today and tomorrow.

HTTP Method: GET

Request Headers: Authorization, Host, Signed-Headers, X-API-Key

Request Payload: None

Response Format: JSON Array

Response Fields:

  • The response is a single Array of multiple Objects, each with the following fields:

    • dateFormat: Provides the standard date format that the system uses.

    • availableDateTime: The start date and time of the appointment, based on the agency’s local timezone.

    • availableDate: Specifically the date part of the time slot.

    • availableTime: Specifically the time part of the time slot.

    • availableRange: Both the start and end date and time of the specific time slot.

    • availabilityCount: The number of spots available at this time slot. Note: in some cases this is null, and in other cases it is zero or negative. These are all valid responses in the context of the system (null means the appointment isn’t time-based, zero means there are no available slots left, and negative means it has been over-booked, which can happen if the appointment caseload is not enforced.)

    • availableTimeslot: Fine-grained details of the date and time for the time slot.

    • distanceKm: The straight-line distance between the Client’s household (if their address is available) and the Agency’s address, in kilometres

    • distanceMi: The straight-line distance between the Client’s household (if their address is available) and the Agency’s address, in miles

    • locationId: The Link2Feed ID of the agency where the time slot is available.

    • location: The name of the agency where the time slot is available.

    • provisions: A collection of Provision options that are available with this visit.

    • postcodeEligibility: A true/false response for whether or not this client is eligible, based on a postcode restriction – if this option is not present, there is no postcode restriction to consider for this program.

    • programId: The ID of the Program that manages the given appointment time slot.

    • programName: The name of the Program that manages the given appointment time slot.

Response Codes:

  • 200 - OK. Response payload includes the data types details.

  • 400 - Bad Request. Will respond with a message including details of the request field errors. For example: { "message": "agencyId field not supplied." }

  • 401 - Unauthorized. There was an issue with the request. Ensure that all required headers are accurate (X-API-Key, Authorization, Signed-Headers and Host).

  • 404 - Not Found. There are no available appointments during the time period of the search, for the agency in question.

  • 5XX - Server Error. Any 5XX error (500, 502, 503, 504) means a Link2Feed error has occurred. Please contact Link2Feed if this persists.

Sample Response for Valid Timeslots:

Code Block
[
    {
        "dateFormat": "Y-m-d H:i",
        "availableDateTime": "2021-02-08 12:15",
        "availableDate": "2021-02-08",
        "availableTime": "12:15",
        "availableRange": {
            "start": "2021-02-08 12:15",
            "end": "2021-02-08 12:45"
        },
        "availabilityCount": 5,
        "availableTimeslot": {
            "year": 2021,
            "month": 2,
            "day": 8,
            "hour": 12,
            "minute": 15,
            "dayName": "Monday"
        },
        "distanceKm": 614.0448277573528,
        "distanceMi": 381.5497667107546,
        "locationId": 8659,
        "location": "Agency 01",
        "provisions": [
            {
                "provisionName": "Sample Food",
                "provisionId": 1509,
                "provisionDisplay": "checkbox"
            },
            ...
        ],
        "postcodeEligibility": false,
        "programId": 4649,
        "programName": "TEFAP Visit"
    },
    ...
]

Book an Appointment for a Client

Purpose: This endpoint attempts to book an appointment for a client, at an agency. The agency in question must have an available appointment slot at the given time and date.

...

Request Payload: JSON Object. The fields of this are all required. Dates and times are based on the agency’s time zone. The date and time fields should match up with a date and time retrieved from the Available Appointments API Endpoint. The clientProfileId field should match up with a client as retrieved from the Client search APIs. programId is only necessary when multiple appointment programs are setup at a single Agency.

Code Block
languagejson
{
	"date": "2021-02-08",
	"time": "12:15",
	"clientProfileId": "e06e0bd4-ceb6-4017-860f-8a8fb03a92c7",
	"programId": 1234
}

Response Format: JSON Object

...