This space documents the general process for authenticating against and utilizing the Client and Appointment API. This is the core API behind our CNCT tool and allows the following interactions:

Any questions, queries or concerns can be forwarded to our support team by emailing support@link2feed.com and requesting assistance. To sign up to use this API, please contact your account manager.

Authentication

All API requests are authenticated using HMAC Signed Requests and an API Key. This is similar to the method that AWS uses for many of its API requests.

Required Credentials

To sign a request, you need the following three pieces of information, provided by Link2Feed:

Required HTTP Headers

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:

      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:

      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.

Notable changes to adapt this elsewhere: pm.variables.get() is used to pull Postman’s environment variables out, and are used here to pull the Secret and URL. If adapting this to other uses, replace the SECRET_KEY variable, and adapt the SIGNED_HEADERS constant to refer to the hostname from a given source.

The signed header that would go into an Authorization header can be produced from a call to, for example, getAuthHeader("GET", "https://example.com/api/v1/demo", '{"sample":"data"}', {"X-API-Key": "6934927105e56d83424ec5bd64", "Host": "example.com", "Signed-Headers": "host,signed-headers"})

Notably below, some shortcuts are taken using the Postman objects. Specifically:

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.

HTTP URL: https://{{ HostName }}/api/v1/datatypes where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed.

HTTP Method: GET

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

Request Payload: Nothing

Response Format: JSON Object

Response Fields:

Response Codes:

Sample Response:

{
    "maritalStatuses": [
        {
            "label": "Single",
            "name": "single"
        },
        {
            "label": "Married",
            "name": "married"
        },
        ,,,
    ],
    "genders": [
        {
            "label": "Female",
            "name": "female"
        },
        ...
    ],
    "housingTypes": [
        {
            "label": "Own House",
            "name": "own_house"
        },
        {
            "label": "Rental",
            "name": "rental"
        },
        ...
    ],
    "ethnicities": [
        {
            "label": "White / Anglo",
            "name": "white_anglo"
        },
        {
            "label": "Black / African American",
            "name": "black"
        },
        {
            "label": "Hispanic / Latino",
            "name": "hispanic_latino"
        },
        ...
    ],
    "selfIdentificationTypes": [
        {
            "label": "Veteran",
            "name": "veteran"
        },
        {
            "label": "Other",
            "name": "other"
        },
        {
            "label": "None",
            "name": "none"
        },
        ...
    ],
    "educationTypes": [
        {
            "label": "Grades 0-8",
            "name": "grade_0_8"
        },
        {
            "label": "Grades 9-11",
            "name": "grade_9_11"
        },
        {
            "label": "High School Diploma",
            "name": "grade_12"
        },
        ...
    ],
    "employmentTypes": [
        {
            "label": "Post Secondary Student",
            "name": "student"
        },
        {
            "label": "Full-Time",
            "name": "full_time"
        },
        {
            "label": "Part-Time",
            "name": "part_time"
        },
        ...
    ],
    "income_sourceTypes": [
        {
            "label": "TEFAP Ineligible",
            "name": "tefap_ineligible"
        },
        {
            "label": "None",
            "name": "none"
        },
        {
            "label": "Pension",
            "name": "pension"
        },
        ...
    ],
    "agencies": [
        {
            "name": "Agency 01 - CSFP",
            "agencyId": 8105,
            "address": {
                "addressLine1": "1400 Independence Avenue SW",
                "addressLine2": "",
                "city": "Washington",
                "state": "Michigan",
                "ward": "District of Columbia",
                "postcode": "20250"
            }
        },
        {
            "name": "Agency 04 - Appointment",
            "agencyId": 7315,
            "address": {
                "addressLine1": "164 Hoard Way",
                "addressLine2": "",
                "city": "Lexington",
                "state": "Kentucky",
                "ward": "Fayette",
                "postcode": "40596"
            }
        },
        ...
    ],
    "relationships": [
        {
            "label": "Spouse",
            "name": "spouse"
        },
        {
            "label": "Child",
            "name": "child"
        },
        {
            "label": "Parent",
            "name": "parent"
        },
        ...
    ],
    "states": {
        "0": "Michigan",
        "1": "California",
        "2": "Texas",
        ...
    },
    "socialProgramTypes": [
        {
            "label": "WIC",
            "name": "wic"
        },
        {
            "label": "Medical Healthcare (Medicare)",
            "name": "medical_healthcare_medicare"
        },
        ...
    ],
    "expenseTypes": [
        {
            "label": "Power",
            "name": "power"
        },
        ...
    ],
    "referredBy": [
        {
            "label": "Child Care Support",
            "name": "Child Care Support"
        },
        {
            "label": "Community Support",
            "name": "Community Support"
        },
        ...
    ],
    "dietaryConsiderationTypes": [
        {
            "label": "Dairy",
            "name": "dairy"
        },
        {
            "label": "Peanut Allergy",
            "name": "peanut_allergy"
        },
        ...
    ],
    "languageTypes": [
        {
            "label": "Abkhazian",
            "name": "Abkhazian"
        },
        {
            "label": "Acoma",
            "name": "Acoma"
        },
        ...
    ],
    "questionPhrasing": {
        "firstName": "<p>First Name&nbsp;</p>",
        "lastName": "Last Name&nbsp;",
        "dob": "Date of Birth (aka DOB) (1)",
        "noFixedAddress": "No Fixed Address / <b>Undisclosed</b>",
        "address": "Address Line 1",
        "address2": "Address Line 2",
        "city": "City1",
        "state": "State",
        "zipCode": "Zipcode",
        "county": "County",
        "gender": "Gender",
        "ethnicities": "What is your ethnicity?",
        "housingType": "Housing Type",
        "education": "Highest Education Level Completed",
        "employment": "Employment Type",
        "selfIdentifiesAs": "Self-Identifies As",
        "incomeType": "Primary Income Type",
        "otherIncomes": "Other Income Sources",
        "maritalStatus": "Marital Status",
        "householdMembers": "Household Members",
        "referredBy": "Referred By",
        "emailAndPhone": "Email and Phone",
        "relationships": "Relationship",
        "socialPrograms": "Receiving the Following Social Programs",
        "expenses": null,
        "dietaryConsiderationTypes": "Dietary Considerations",
        "languageTypes": null,
        "password1": "<p>Please Enter your <b>Password</b></p>",
        "password2": "Please Confirm your <b>Password</b>"
    },
    "welcomeMessage": "<p>WELCOME</p>",
    "informationDisclaimer": "Testing",
    "consentDisclaimer": "CONTACT CONSENT",
    "householdMessage": "<strike style=\"\">CLIENT</strike> HOUSHOLD DETAILS",
    "thankYouMessage": "THANKS!",
    "imageUrl": "https://www.link2feed.com/wp-content/uploads/2016/08/L2F-Logo.png",
    "foodBankName": "Live Testing Network",
    "enabledFields": [
        "clientProfileId",
        "addressLine1",
        "city",
        "state",
        "zipCode",
        "addressLine2",
        "county",
        "gender",
        "employmentType",
        "housingType",
        "educationType",
        "ethnicity",
        "selfIdentificationType",
        "maritalStatus",
        "incomeType",
        "income",
        "relationship",
        "otherIncomes",
        "socialProgramTypes",
        "referredBy",
        "dietaryConsiderationTypes"
    ],
    "dateFormat": "MM-dd-yyyy",
    "useAutocomplete": true,
    "useUSPSAutocomplete": false
}

Clients API

Purpose: This collection of endpoints are used to create and update client profiles.

Find a Client by Minimal Details (First Name, Last Name and Date of Birth)

Purpose: This endpoint finds existing clients, or, failing to find an existing client, will return a new session key to begin interacting with the API to build a new client.

HTTP URL: https://{{ HostName }}/api/v1/clients/find where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed. Note: the URL Query Parameter skipDupecheck=truecan be appended to this URL (eg- .../clients/find?skipDupecheck=true) in order to skip duplicate checking. This may be desired to avoid potential privacy issues, depending on the access level of the tools utilizing this API.

HTTP Method: POST

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

Request Payload: JSON Object
This object has three fields. If any of them are missing, the request will fail. These represent the client’s first name, last name, and date of birth. With these three identifiers most clients in the system can be uniquely identified.

{
	"firstName":"Sample",
	"lastName":"Client",
	"dob":"1993-03-03"
}

Response Format: JSON Object

Response Fields:

Response Codes:

Sample Response for No Client Found:

{
    "clientProfileId": "9c5d7ccc-5874-4bfe-8a5b-97b70b1e932b",
    "filters": []
}

Sample Response for Found Single Client:

{
    "clientProfileId": "37628109-abc1-41ab-891c-27175825d5cd",
    "link2feedId": 10275991,
    "filters": []
}

Sample Response for Found Multiple Clients:

{
    "clientProfileId": "e7676973-7c29-4c86-bf1d-0a3377bb9c25",
    "filters": [
        {
            "type": "text",
            "key": "address",
            "text": "What is your address?",
            "values": [
                "9349 S********d C***t",
                "300 M**n S*",
                "503 S*******e P***a",
                "263 C*******n C****r",
                "146 S****y T*****e",
                "200 M**n A*e S*",
                "474 B***y T*****e"
            ]
        }
    ]
}

Find a Client by Filters

Purpose: This endpoint potentially chooses a client from the potential filters. For example, the previous API endpoint could produce 7 address options. If this endpoint receives the correct session ID and address option to match an existing client, it will respond with a valid client option. If one of the invalid options is selected, however, it will respond with a new client session.

HTTP URL: https://{{ HostName }}/api/v1/clients/findbyfilters where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed.

HTTP Method: POST

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

Request Payload: JSON Object
This object has two top-level fields. If any of them are missing, the request will fail. These represent the client’s GUID or profile ID, which is also the session key for working with this client, and also the specific filters chosen from previous filters.

{
    "clientProfileId": "03885d38-1e22-40a1-a81c-9caf564057f4",
	"filters": [
		{
			"key": "address",
			"value": "300 M**n S*"
		}
	]
}

Response Format: JSON Object

Response Fields:

Response Codes:

Sample Response:

{
    "clientProfileId": "e06e0bd4-ceb6-4017-860f-8a8fb03a92c7"
}

Find a Client by ID (Client Profile ID or Link2Feed ID)

Purpose: This endpoint finds existing clients or fails loudly if it cannot find the client. This is in sharp comparison to the other client-finding endpoints which fail silently and encourage moving down the process of creating a new profile. This endpoint can be used to ensure that a client still exists in the Link2Feed system. This may not always be the case as clients are merged to resolve duplicate profile issues. This endpoint can also be used to convert Client Profile IDs to Link2Feed IDs and back.

HTTP URL: https://{{ HostName }}/api/v1/clients/{{ ID }} where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed. {{ ID }} is either the link2feedId or clientProfileId provided by any of the previous calls.

HTTP Method: GET

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

Request Payload: None

Response Format: JSON Object

Response Fields:

Response Codes:

Sample Response for Found Single Client:

{
    "clientProfileId": "37628109-abc1-41ab-891c-27175825d5cd",
    "link2feedId": 10275991
}

Find a Client’s Existing Details by ID (Client Profile ID or Link2Feed ID)

Purpose: This endpoint finds existing clients or fails loudly if it cannot find the client. This is in sharp comparison to the other client-finding endpoints which fail silently and encourage moving down the process of creating a new profile. This endpoint also provides a robust set of client personal information, and information for their household.

HTTP URL: https://{{ HostName }}/api/v1/clients/{{ ID }}/details where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed. {{ ID }} is either the link2feedId or clientProfileId provided by any of the previous calls.

HTTP Method: GET

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

Request Payload: None

Response Format: JSON Object

Response Fields:

Several of the following fields refer to the data types retrieved from the data types API endpoint.

Response Codes:

Sample Response for Found Single Client:

{
    "clientProfileId": "e06e0bd4-ceb6-4017-860f-8a8fb03a92c7",
    "link2feedId": 1029665,
    "firstName": "Sample",
    "lastName": "Person",
    "dateOfBirth": "01-01-1980",
    "dobEstimated": false,
    "agencyId": 8105,
    "noFixedAddress": "",
    "addressLine1": "300 Main St",
    "addressLine2": null,
    "city": "Gaithersburg",
    "state": "Maryland",
    "county": "Montgomery",
    "zipCode": "20878",
    "gender": "male",
    "employmentType": "declined_to_answer",
    "educationType": "do_not_know",
    "ethnicity": [
        "declined_to_answer"
    ],
    "housingType": "individual",
    "selfIdentificationType": [
        "do_not_know"
    ],
    "maritalStatus": "did_not_ask",
    "income": "0.00",
    "incomeType": "odsp",
    "otherIncomes": [],
    "email": false,
    "phoneNumber": false,
    "referredBy": "Emergency Shelter",
    "socialProgramTypes": [
        "festival_program"
    ],
    "expenseTypes": [],
    "dietaryConsiderationTypes": [
        "did_not_ask"
    ],
    "languageTypes": [],
    "householdMembers": [
        {
            "clientProfileId": "d5ace4df-630a-45e8-a031-0eb93358a7b9",
            "link2feedId": 1029666,
            "firstName": "Other",
            "lastName": "Person",
            "dateOfBirth": "04-01-1999",
            "dobEstimated": false,
            "agencyId": 8105,
            "noFixedAddress": "",
            "addressLine1": "300 Main St",
            "addressLine2": null,
            "city": "Gaithersburg",
            "state": "Maryland",
            "county": "Montgomery",
            "zipCode": "20878",
            "gender": "other",
            "employmentType": "student",
            "educationType": "grade_9_11",
            "ethnicity": [
                "other"
            ],
            "housingType": "individual",
            "selfIdentificationType": [
                "other"
            ],
            "maritalStatus": "single",
            "income": 0,
            "incomeType": 0,
            "otherIncomes": [],
            "email": false,
            "phoneNumber": false,
            "referredBy": "Emergency Shelter",
            "socialProgramTypes": [],
            "expenseTypes": [],
            "dietaryConsiderationTypes": [],
            "languageTypes": [],
            "relationship": "other"
        }
    ]
}

Add or Update Clients In the System

Purpose: This endpoint will allow either adding new clients (POST) or updating existing clients (PATCH). The fields required are listed in the data types API as enabledFields.

HTTP URL: https://{{ HostName }}/api/v1/clients where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed.

HTTP Method: POST to add, or PATCH to update

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

Request Payload: JSON Object
This object has a variable number of fields, depending on the setup of the individual environment. The expected fields are listed in the data types API response as enabledFields. In general, a valid response to the Client Details API is also a valid request payload for the add/update API.

Sample Request Payload

{
    "clientProfileId": "e06e0bd4-ceb6-4017-860f-8a8fb03a92c7",
    "link2feedId": 1029665,
    "firstName": "Sample",
    "lastName": "Person",
    "dateOfBirth": "01-01-1980",
    "dobEstimated": false,
    "agencyId": 8105,
    "noFixedAddress": "",
    "addressLine1": "300 Main St",
    "addressLine2": null,
    "city": "Gaithersburg",
    "state": "Maryland",
    "county": "Montgomery",
    "zipCode": "20878",
    "gender": "male",
    "employmentType": "declined_to_answer",
    "educationType": "do_not_know",
    "ethnicity": [
        "declined_to_answer"
    ],
    "housingType": "individual",
    "selfIdentificationType": [
        "do_not_know"
    ],
    "maritalStatus": "did_not_ask",
    "income": "0.00",
    "incomeType": "odsp",
    "otherIncomes": [],
    "email": false,
    "phoneNumber": false,
    "referredBy": "Emergency Shelter",
    "socialProgramTypes": [
        "festival_program"
    ],
    "expenseTypes": [],
    "dietaryConsiderationTypes": [
        "did_not_ask"
    ],
    "languageTypes": [],
    "householdMembers": [
        {
            "clientProfileId": "d5ace4df-630a-45e8-a031-0eb93358a7b9",
            "link2feedId": 1029666,
            "firstName": "Other",
            "lastName": "Person",
            "dateOfBirth": "04-01-1999",
            "dobEstimated": false,
            "agencyId": 8105,
            "noFixedAddress": "",
            "addressLine1": "300 Main St",
            "addressLine2": null,
            "city": "Gaithersburg",
            "state": "Maryland",
            "county": "Montgomery",
            "zipCode": "20878",
            "gender": "other",
            "employmentType": "student",
            "educationType": "grade_9_11",
            "ethnicity": [
                "other"
            ],
            "housingType": "individual",
            "selfIdentificationType": [
                "other"
            ],
            "maritalStatus": "single",
            "income": 0,
            "incomeType": 0,
            "otherIncomes": [],
            "email": false,
            "phoneNumber": false,
            "referredBy": "Emergency Shelter",
            "socialProgramTypes": [],
            "expenseTypes": [],
            "dietaryConsiderationTypes": [],
            "languageTypes": [],
            "relationship": "other"
        }
    ]
}

Response Format: JSON Object

Response Fields:

Several of the following fields refer to the data types retrieved from the data types API endpoint.

Response Codes:

Sample Response for Job Completed:

{
    "clientProfileId": "e06e0bd4-ceb6-4017-860f-8a8fb03a92c7",
    "link2feedId": 1029665
}

Appointments 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

Request Payload: None

Response Format: JSON Array

Response Fields:

Response Codes:

Sample Response for Valid Timeslots:

[
    {
        "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,
                "streetAddress": "1234 Main Ave, Detroit, Michigan, 48216, United States"
            },
            ...
        ],
        "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:

Response Codes:

Sample Response for Valid 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"
        },
        "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",
        "streetAddress": "1234 Main Ave, Detroit, Michigan, 48216, United States"
    },
    ...
]

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.

HTTP URL: https://{{ HostName }}/api/v1/agencies/{{ Agency ID }}/appointments 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, and also listed as both agencyId and locationId from the Available Appointments API Endpoint.

HTTP Method: POST

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

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.

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

Response Format: JSON Object

Response Fields:

Response Codes:

Sample Response for A Successfully Booked Appointment:

{
    "appointmentId": "2942289"
}

Update or Cancel an Appointment

Purpose: This endpoint attempts to update or cancel an existing appointment for a client, at an agency.

HTTP URL: https://{{ HostName }}/api/v1/agencies/{{ Agency ID }}/appointments/{{ Appointment 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, and also listed as both agencyId and locationId from the Available Appointments API Endpoint. The {{ Appointment ID }} represents an existing appointment.

HTTP Method: POST to update, or DELETE to cancel.

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

Request Payload: JSON Object. For updates, the fields 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. No payload is required for a cancellation.

{
	"date": "2021-02-08",
	"time": "12:15"
}

Response Format: JSON Object. The response will be empty for a deletion.

Response Fields:

Response Codes:

Sample Response for A Successfully Updated Appointment:

{
    "appointmentId": "2942289"
}

Mark an Appointment Collected

Purpose: This endpoint attempts to mark an existing appointment as collected for a client, at an agency.

HTTP URL: https://{{ HostName }}/api/v1/agencies/{{ Agency ID }}/appointments/{{ Appointment 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, and also listed as both agencyId and locationId from the Available Appointments API Endpoint. The {{ Appointment ID }} represents an existing appointment.

HTTP Method: PATCH.

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

Request Payload: Empty

Response Format: JSON Object. The response will be an empty object for successfully updated appointments.

Response Fields:

Response Codes:

Sample Response for A Successfully Updated Appointment:

[]

Retrieve Upcoming Appointments for a Client

Purpose: This endpoint retrieves a list of upcoming appointments for a client.

HTTP URL: https://{{ HostName }}/api/v1/clients/{{ Client ID }}/upcoming-appointments where {{ HostName }} is provided by Link2Feed, and varies depending on the environment (Test, Live, Staging, etc) being accessed. {{ Client ID }} is the numeric Link2Feed ID from any of the client search API endpoints.

HTTP Method: GET

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

Request Payload: None

Response Format: JSON Array. The array will consist of appointment objects, if the client has any upcoming appointments.

Response Fields:

Response Codes:

Sample Response for A Successfully Updated Appointment:

[
    {
        "id": "2942289",
        "date": "2021-02-08",
        "time": "12:15",
        "datetime": "2021-02-08 12:15",
        "location": {
            "id": 8659,
            "name": "Agency 01",
            "address": {
                "addressLine1": "400 Main St",
                "addressLine2": "",
                "city": "Pawtucket",
                "state": "Rhode Island",
                "postcode": "02860"
            }
        }
    },
    {
        "id": "2942289",
        "date": "2021-02-09",
        "time": "12:15",
        "datetime": "2021-02-09 12:15",
        "location": {
            "id": 8659,
            "name": "Agency 01",
            "address": {
                "addressLine1": "400 Main St",
                "addressLine2": "",
                "city": "Pawtucket",
                "state": "Rhode Island",
                "ward": "Providence",
                "postcode": "02860"
            }
        }
    },
    ...
]

Appendix A: Postman Collection Files

The following files represent the above API endpoints using the Postman API testing and requesting tool (https://www.postman.com/).

To utilize these effectively and easily perform the HMAC signing without having to manually recalculate each request, you will need to setup an environment within Postman that includes at least:

To learn more about setting environments in Postman see: https://learning.postman.com/docs/sending-requests/managing-environments/