# Introduction

Lingk provides recipes to make data integration more agile and simplify your data processes. Lingk scales to your largest datasets, handles your scheduled and event-based processes, and can integrate with existing tools and platforms using webhooks.

## The Lingk Platform

### [Recipes](/transformation_recipes)

Recipes are the heart of the Lingk Platform. Each recipes starts with connectors (to APIs, files, and cloud apps) and SQL statements. These YAML-based recipes are REST API events or time-based schedules.

All recipes are executed by the Lingk Spark Integration Recipe Engine (SIRE). SIRE is an Apache Spark application designed that runs on Amazon EMR and other Spark installations. The Lingk SaaS platform provides you immediate access to the power of Apache Spark through recipes.

Reusable recipes can be shared in public or private recipe libraries to make it easier for anyone to build integrations in your enterprise or on your platform.

### [REST APIs](/lingk-apis)

Lingk REST APIs can be embedded in recipes, in Salesforce CRM, or your tool to trigger powerful data integration and processing on-demand. Additionally, you can leverage event subscriptions to hook into event listeners from your application, Zapier or other workflow integration tools.

### [API Plugin for Apache Nifi](http://help.lingk.io/en/articles/107-on-premise-adapter-overview)

Lingk's API Plugin for Apache Nifi provides an out of the box API set of templates and custom NARs designed to make it easy to build APIs from databases, files and other legacy systems.


# Recipes

Recipes enable simple and reusable integrations between cloud, file-based and on-premise data sources. Each Recipe consists of connectors and statements. Recipes are triggered by REST API events or on time-based schedules.

Recipes are:

* Shared on the Lingk [Public Recipe Library](https://lingk.io/recipes).
* Built in the Recipe Editor and Designer for custom scenarios.
* Generated by the Visual SQL Wizard for quick Salesforce data loading.
* Executed by Apache Spark.

Full documentation on building recipes is available via the Lingk [knowledge base](http://help.lingk.io/). Learn the basics with the article entitled "[Writing a Recipe](https://help.lingk.io/en/articles/21)".


# Using Recipes with Lingk APIs

You can use Lingk API inside of recipe to hook in Lingk events that trigger other recipes or integrate with Zapier via webhooks. With Lingk recipes, you don't need to write any code to integrate your data with events.

To view and run some recipes that demonstrate how this is done visit the Lingk Public Recipe Library and search for "event".

Here are few recipe example to get you started: 1. [Trigger Lingk Events using a Recipe](http://www.lingk.io/recipes#recipe-4DWfhb1ziok4UCa6CiQSym) (beginner) 2. [Data Diff Delta on Full Datasets and Send to Event-Driven Webhook Subscriptions](http://www.lingk.io/recipes#recipe-56m7j9PjlmsG60SuIGUEI6) (intermediate)


# Lingk REST APIs

## Using the Lingk REST APIs

### Table of Contents

* [Getting Started](/lingk-apis#start)
* [Authentication](/lingk-apis#authentication)
  * [API Context](/lingk-apis#context)&#x20;
* [Paging](/lingk-apis#paging)        &#x20;
* [Filtering](/lingk-apis#filtering) &#x20;
  * [By String Values](/lingk-apis#string) &#x20;
  * [By Date Values](/lingk-apis#date) &#x20;
  * [Compound Filters](/lingk-apis#compound)    &#x20;
* [API Errors](/lingk-apis#errors)                           &#x20;

## Getting Started

With Lingk's data integration platform, you can centralize data, integration and documentation. Each Account has a dedicated API endpoint and credentials.

## Authentication

### HMAC signatures

HMAC Signing is an access token method that adds another level of security by forcing the requesting client to also send along a signature that identifies the request temporarily to ensure that the request is from the requesting user, using a secret key that is never broadcast over the wire.

The [HTTP Signatures standard](https://tools.ietf.org/html/draft-cavage-http-signatures-05) provides a standardized way of signing HTTP requests. Lingk supports the HTTP Signatures standard using SHA-1 encrypted HMACs. Signatures expire 10 seconds after creation.

An HMAC signature is essentially some additional data sent along with a request to identify the end-user using a hashed value. In our case, we encode the 'date' header, HTTP method, and endpoint of a request, the algorithm would look like:

`urlEncode(base64Encode(SHA1("date:Mon, 02 Jan 2006 15:04:05 MST\n(request-target): GET /v1/ps/@self/contacts”, secret_key))`

The full request header for an HMAC request uses the standard Authorization header, and uses set, stripped comma-delimited fields to identify the user, from the draft proposal:

`Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",headers="date (request-target)",signature="urlEncode(base64Encode(SHA1("date: Mon, 02 Jan 2006 15:04:05 MST\n(request-target): GET /v1/ps/@self/contacts”, secret_key))"`

The date format for an encoded string is:

`Mon, 02 Jan 2006 15:04:05 MST`

This data format is the standard for most browsers, but it worth noting that if the above format is not how the `date` HTTP header is encoded the request will fail.

A curl example of a valid request:

```
curl -n 
-H 'Date: Wed, 25 May 2016 16:06:06 GMT' 
-H 'Authorization: Signature keyId="[yourkey]",algorithm="hmac-sha1",headers="date (request-target)",signature="a4JGzTlsExiTXVCvtZjIGjf6m50%3D"'
 https://www.lingkapis.com/v1/@self/ps/calendarsessions
```

For implementations in various languages, go to [Code Samples](https://developers.lingk.io/code_samples.html).

### API Context

The context of an API request for Lingk.

#### Attributes

| Name          | Type     | Description                                                             | Example           |
| ------------- | -------- | ----------------------------------------------------------------------- | ----------------- |
| **self**      | *string* | Reference to authentication context.                                    | `"@self"`         |
| **tenantKey** | *string* | Unique key for the tenant. See the Developer Console for your key.      | `"centralu"`      |
| **appKey**    | *string* | Unique key for the application. See the Developer Console for your key. | `"studentsystem"` |

## API Errors

When an exception occurs, an HTTP code will be returned and a JSON response. In the JSON response, will be an error code.

| HTTP Code | Error Code | Description                                                                                             |   |
| --------- | ---------- | ------------------------------------------------------------------------------------------------------- | - |
| 409       | 102        | Validation: Conflict                                                                                    |   |
| 409       | 103        | Validation: Conflict                                                                                    |   |
| 412       | 104        | Validation: Precondition failed. Often related to using an invalid External ID.                         |   |
| 400       | 105        | Validation: Precondition failed. Often related to using an invalid Tenant or App in Data Consumer URIs. |   |
| 500       | 106        | System: Internal Error                                                                                  |   |
| 401       | 107        | Validation: Inaccessible resource. You may not have access to this resource.                            |   |
| 422       |            | Validation: Unprocessable entity. Check your JSON format.                                               |   |

## Paging

Lingk APIs return data in pages. Each page is by default sorted by `lastModifiedDate`.

| Name       | Type     | Description                                                 | Example |
| ---------- | -------- | ----------------------------------------------------------- | ------- |
| **limit**  | *string* | Number of records returned per GET request. Defaults to 25. | `25`    |
| **offset** | *string* | Page of data to return. Defaults to 1.                      | `2`     |

### Use Cases

* Returning data for the second page

  ```
  ?offset=2
  ```
* Returning more than 25 records per page

  ```
  ?limit=100
  ```
* Returning data for another page with a defined limit

  ```
  ?offset=2&limit=100
  ```

## Filtering

Lingk APIs allow filtering by string or data values. Lingk supports advanced filtering scenarios through the `filter` parameters. Common supported scenarios are:

* Selecting sections for a given calendar session

  ```
  &filter=calendarSessionExternalId eq winter2015
  ```
* Selecting updated section information

  ```
  &filter=lastModifiedDate gte 2015-01-02T15:04:05-07:00
  ```

### By String Value

Lingk supports filtering by string values. The `filter` query string parameter supports the following string operations.

| Name   | Type     | Description | Example                                             |
| ------ | -------- | ----------- | --------------------------------------------------- |
| **eq** | *string* | Equals      | `"&filter=calendarSessionExternalId eq winter2015"` |

### By Date Value

Lingk supports filtering by RFC3339 formatted date values. The `filter` query string parameter supports the following date operation.

| Name    | Type   | Description           | Example                                                   |
| ------- | ------ | --------------------- | --------------------------------------------------------- |
| **lt**  | *date* | Less Than             | `"&filter=lastModifedDate lt 2015-01-02T15:04:05-07:00"`  |
| **lte** | *date* | Less Than or Equal    | `"&filter=lastModifedDate lte 2015-01-02T15:04:05-07:00"` |
| **gt**  | *date* | Greater Than          | `"&filter=lastModifedDate gt 2015-01-02T15:04:05-07:00"`  |
| **gte** | *date* | Greater Than or Equal | `"&filter=lastModifedDate gte 2015-01-02T15:04:05-07:00"` |

### Compound Filters

Multiple filters can be used in a single request. Each filter is added as a separate `filter` query string parameter.

`&filter=lastModifedDate gt 2015-01-02T15:04:05-07:00&filter=courseNumber eq 101_avc`

Retrieves all Courses modified since 2015-01-02 that have a Course Number equalling "101\_avc"


# Using Postman with Lingk APIs

You can use Postman to test Lingk API calls before embedding them into Lingk recipes or into your application.

## Example configuration of an HTTP POST to the Lingk Event REST API

![](/files/-M3s1jS2trYW256SrGOh)

In an environment, create the following variables:

* `client-key`
* `client-secret`

The values for `client-key` and `client-secret` from your Lingk Workspace's "Overview" > "Settings" tab.

## The *Pre-request Script* for Lingk Auth

```javascript
function computeHttpSignature(config, headerHash) {
  var template = 'keyid="${keyId}",algorithm="${algorithm}",headers="${headers}",signature="${signature}"',
      sig = template;

  console.log(template); 
  console.log(headerHash); 

  // compute sig here
  var signingBase = '';
  config.headers.forEach(function(h){
    console.log(h);
    if (signingBase !== '') { signingBase += '\n'; }
    signingBase += h.toLowerCase() + ": " + headerHash[h];
  });

  console.log(signingBase); 

  var hashf = (function() {
      switch (config.algorithm) {
        case 'hmac-sha1': return CryptoJS.HmacSHA1;
        case 'hmac-sha256': return CryptoJS.HmacSHA256;
        case 'hmac-sha512': return CryptoJS.HmacSHA512;
        default : return null;
      }
    }());

  console.log("hashAlgorithm: " + config.algorithm);

  var hash = hashf(signingBase, config.secretkey);

  console.log("hash: " + hash);

  var signatureOptions = {
        keyId : config.keyId,
        algorithm: config.algorithm,
        headers: config.headers,
        signature : encodeURIComponent(CryptoJS.enc.Base64.stringify(hash))
      };

  console.log(signatureOptions); 

  // build sig string here
  Object.keys(signatureOptions).forEach(function(key) {
    var pattern = "${" + key + "}",
        value = (typeof signatureOptions[key] != 'string') ? signatureOptions[key].join(' ') : signatureOptions[key];
    sig = sig.replace(pattern, value);
  });
   console.log(sig); 

  return sig;
}


var curDate = new Date().toUTCString();
var targetUrl = request.url.trim(); // there may be surrounding ws
targetUrl = targetUrl.replace(new RegExp('^https?://[^/]+/'),'/'); // strip hostname
var method = request.method.toLowerCase();
var sha256digest = CryptoJS.SHA256(request.data);
var base64sha256 = CryptoJS.enc.Base64.stringify(sha256digest);
var computedDigest = 'sha-256=' + base64sha256;

var headerHash = {
      date : curDate,
      '(request-target)' : method + ' ' + targetUrl
    };

var config = {
      algorithm : 'hmac-sha1',
      keyId : environment['client-key'],
      secretkey : environment['client-secret'],
      headers : [ 'date', '(request-target)'  ]
    };

var sig = computeHttpSignature(config, headerHash);

postman.setEnvironmentVariable('httpsig', sig);
postman.setEnvironmentVariable("current-date", curDate);
```

## Troubleshooting

When the API response is an HTTP 410 "Clock skew outside of acceptable bounds", you may be behind a proxy server which filters the "Date" header. Therefore add an "x-aux-date" header and apply the same date header variable.

![](/files/-M3s1jS8HfECdSFomAED)


# Events & Webhooks

Lingk moves your periodic, scheduled integration processes to data-driven events that fit into your business process. Lingk's Event Broker connects real-time data and events with monitoring, analysis, communication, and learning systems.

Here are a few scenarios:

* A Lingk real-time recipe is triggered based on a learning event consumed by another HTTP endpoint.
* As files are transformed, an error occurs due to a schema change in the file. In real-time, a specific event that is subscribed to by a email notification engine and the bug tracking system is created.
* As education data is transformed by the Transformer Engine, events are added to queues for grading, retention, and alerting purposes.

To see an example Node.js project go to: <https://github.com/lingkio/event-triggered-recipe-nodejs>

## **Create a subscription**

```
curl -X POST  
-H "date: Wed, 27 Jul 2016 02:38:11 UTC"  
-H 'Authorization: Signature keyId="asfsdfaf",algorithm="hmac-sha1",headers="date (request-target)",signature="WCIW%2F%2B0WG8tIvpQ0W7t34kksj5Q%3D"' 
-d @subscription.json
https://www.lingkapis.com/v1/@self/webhooks/subscriptions

{
 "tenantKeyFilter": "lingkuni",
 "appKeyFilter": "peoplesoftsis",
 "objectTypeFilter": "*",
 "verbFilter": "*",
 "deliveryUrl": "http://some.lingkto.com/test/webhookdelivery"
}
```

## **Delete a subscription**

```
curl -X DELETE  
-H "date: Wed, 27 Jul 2016 02:39:44 UTC"  
-H 'Authorization: Signature keyId="dfsadfasf",algorithm="hmac-sha1",headers="date (request-target)",signature="wBRwzLuW%2BCXblCFEJtjO9YSjXgw%3D"' 
https://www.lingkapis.com/v1/@self/webhooks/subscriptions/e029485bf8ba4c6ba11b2a62ec208af7
```

## **Get subscriptions**

```
curl -X GET  
-H "date: Wed, 27 Jul 2016 02:41:42 UTC"  
-H 'Authorization: Signature keyId="dfsadfasf",algorithm="hmac-sha1",headers="date (request-target)",signature="oDEh0diKNuzzHSBYXW3tAVywd0g%3D"' 
https://www.lingkapis.com/v1/@self/webhooks/subscriptions
```

## **Post an event**

```
curl -X POST  
-H "date: Wed, 27 Jul 2016 02:43:21 UTC"  
-H 'Authorization: Signature keyId="dfsadfasf",algorithm="hmac-sha1",headers="date (request-target)",signature="V3dgtmkNLvxq7QNPx4kXcSuFU0k%3D"' 
-d @event.json https://www.lingkapis.com/v1/@self/events

 {
    "verb":"create",
    "objectType":"fileUpload",
    "eventObject": { "file":
      { "path": "/path.zip" } 
    }
}

 {
    "verb":"add",
    "objectType":"learningEvent",
    "eventObject": { 
    ... Capliper/xAPI statement ...
    }
}
```

## **Consume Events via a Webhook**

After an event is triggered through the Event Broker, all subscribed endpoints are *pushed* a JSON payload for the event.

The eventObject property contains the data associated with the event. Lingk has a specific eventObject format for files imported into a Data Repository.

Example Webhook Payload

```
{
  "appKey": "courserosters",
  "eventGuid": "538c8a6e2e44477a84e85dbeb921bef9",
  "eventObject": {
    "course": {
      "courseCode": "24617.1101"
    }
  },
  "objectType": "integration.courserosters.testevent",
  "tenantKey": "integration",
  "timestamp": "2016-10-21T06:11:40.267817769Z",
  "verb": "finish"
}
```


# Code Samples

Use the code samples in this section to easily get started working with Lingk.

* [C#](/lingk-apis/code_samples/code_samples_csharp)
* [ColdFusion](/lingk-apis/code_samples/code_samples_coldfusion)
* [Go](/lingk-apis/code_samples/code_samples_go)
* [Java](/lingk-apis/code_samples/code_samples_java)
* [Node.js](/lingk-apis/code_samples/code_samples_nodejs)
* [PHP](/lingk-apis/code_samples/code_samples_php)
* [Python](/lingk-apis/code_samples/code_samples_python)
* [Salesforce Apex](/lingk-apis/code_samples/salesforce-apex)


# C\#

```csharp
using System;
using System.Security.Cryptography;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web;
​
public class Program
{
    static String entrypoint = "http://www.lingkapis.com";
    static String apikey = "[your key]";
    static String secret = "[your secret]";
    private static readonly Encoding encoding = Encoding.UTF8;
​
    public static void Main()
    {
        Program.servicecall("/v1/@self/ps/courses", "GET");
        Console.Read();
    }
​
    public static async void servicecall(String service, String metho)
    {
        String requestDate;
        UriBuilder uri = new UriBuilder(entrypoint + service);
        Console.WriteLine(uri);
​
        requestDate = DateTime.UtcNow.ToString("R");

        HttpClientHandler handler = new HttpClientHandler() { };
        string fullMessage = "date: " + requestDate + "\n(request-target): " + method.toLower() + " " + service;
        using (var client = new HttpClient(handler))
        {
​
            string hashString = CreateSignature(fullMessage, secret);
            String authHeader = "Signature keyId=\"" + apikey + "\",headers=\"date (request-target)\",algorithm=\"hmac-sha1\",signature=\"" + WebUtility.UrlEncode(hashString) + "\"";

            client.BaseAddress = new System.Uri(entrypoint);
            client.DefaultRequestHeaders.Add("Date", requestDate);
            client.DefaultRequestHeaders.Add("Authorization", authHeader);
            var resp2 = await client.GetAsync(service);
            var aaa = resp2.Content;
            string result = await aaa.ReadAsStringAsync();
            Console.WriteLine(result);
​
        }
    }
​
    private static string CreateSignature(string message, string secret)
    {
        secret = secret ?? "";
        var encoding = new System.Text.ASCIIEncoding();
        byte[] keyByte = encoding.GetBytes(secret);
        byte[] messageBytes = encoding.GetBytes(message);
        using (var hmacsha1 = new HMACSHA1(keyByte))
        {
            byte[] hashmessage = hmacsha1.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashmessage);
        }
    }
​
}
```


# ColdFusion

```markup
<html>
 <body style="font-family:arial">

<CFSCRIPT>
    key = "[your key]";
    secret = "[your secret]";

    currentDate = DateConvert("local2utc", now());
    currentDate = dateTimeFormat(currentDate, 'EEE, dd mmm yyyy HH:nn:ss', "GMT");
    currentDate = currentDate & " GMT";

    endpoint =  "https://www.lingkapis.com";
    service =  "/v1/@self/ps/courses";
    method =  "get";

    message =  "date: " & currentDate & "\n(request-target): " & method & " " & service;

    // Use new built-in Hmac() method.
    sigAsHex = hmac( message, secret, "HMACSHA1", "utf-8");

    sig = binaryEncode( binaryDecode(sigAsHex, "hex"), "base64" );

</CFSCRIPT>
<br>
<cfset authHeader = "Signature keyId=""#key#"",headers=""date (request-target)"",algorithm=""hmac-sha1"",signature=""#URLEncodedFormat(sig)#""">
<cfoutput>#authHeader#</cfoutput>
<br>
<cfhttp method="#method#" url="#endpoint##service#" result="result">
    <cfhttpparam type="header" name="Authorization" value="#authHeader#" />
    <cfhttpparam type="header" name="Date" value="#currentDate#" />    
</cfhttp>

<cfdump var="#result#" />

</body>
</html>
```


# Go

```go
import (
  "time"
  "fmt"
  "crypto/hmac"
  "crypto/sha1"
  "encoding/base64"
  "net/url"
  "os"
)

func main() {
  endpoint := os.Args[1]
  method := os.Args[2]

  theUrl, _ := url.Parse(endpoint)

  keyId := "[your key]"
  secret64 := "[your secret]"
  secret := []byte(secret64)

  now := time.Now().UTC()
  formatted := now.Format("Mon, 02 Jan 2006 15:04:05 MST")
  dateHeader := " -H \"Date: " + formatted + "\""

  mac := hmac.New(sha1.New, secret)
  mac.Write([]byte("date: " + formatted + "\n(request-target): " + strings.ToLower(method) + " " + theUrl.Path))
  macBytes := mac.Sum(nil)

  macEncoded := url.QueryEscape(base64.StdEncoding.EncodeToString(macBytes))
  signatureHeader := " -H 'Authorization: Signature keyId=\"" + keyId +
     "\",headers=\"date (request-target)\",algorithm=\"hmac-sha1\",signature=\"" + macEncoded + "\"'"

  if method == "POST" {
      fmt.Println("curl -n -X " + method + " -d @json.txt " + dateHeader + " " + signatureHeader +
      " " + endpoint + service)
  } else {
      fmt.Println("curl -n " + dateHeader + " " + signatureHeader + developerIdHeader +
      " " + endpoint + service)
  }
}
```


# Java

```java
package io.lingk.sampleclient;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class SampleClient {

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
        String endpoint = "https://www.lingkapis.com/v1/@self/ps/students";

        String keyId = "[your key]";
        String secret64 = "[your secret]";
        byte[] secret = secret64.getBytes();

        URL url = new URL(endpoint);
        String requestMethod = "GET";  // GET, POST, PUT, DELETE

        // get current time in UTC
        ZonedDateTime nowInUtc = ZonedDateTime.now(Clock.systemUTC());
        String formattedDate = nowInUtc.format(DateTimeFormatter.RFC_1123_DATE_TIME);

        // create a signing string to sign
        String signingString = "date: " + formattedDate + "\n(request-target): " + requestMethod.toLowerCase() + " " + url.getPath();

        // sign the signing string
        SecretKeySpec sks = new SecretKeySpec(secret, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(sks);

        byte[] hmac = mac.doFinal(signingString.getBytes());
        String encodedHMAC = URLEncoder.encode(Base64.getEncoder().encodeToString(hmac), "ASCII");

        // construct authorization header
        String authorizationHeader = "Signature keyId=\"" + keyId + "\",headers=\"date (request-target)\",algorithm=\"hmac-sha1\",signature=\"" + encodedHMAC + "\"";

        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(endpoint);
        httpGet.addHeader("Date", formattedDate);
        httpGet.addHeader("Authorization", authorizationHeader);
        CloseableHttpResponse response1 = httpclient.execute(httpGet);
        try {
             System.out.println(response1.getStatusLine());
            HttpEntity entity1 = response1.getEntity();
            InputStream is = entity1.getContent();
            String theString = IOUtils.toString(is, "ASCII"); 
            System.out.println(theString);
            EntityUtils.consume(entity1);
        } finally {
            response1.close();
        }
    }

}
```


# Node.js

A simple express app with the following dependencies:

```javascript
  "dependencies": {
    "express": "^4.13.4",
    "nodedump": "*",
    "dateformat": "*"
  }
```

To see an example Node.js project go to: <https://github.com/lingkio/event-triggered-recipe-nodejs>

Example app

```javascript
var express = require('express');
var crypto = require("crypto"); 
var https = require('https');
var qs = require('querystring');
var dateFormat = require('dateformat');
var nodedump = require('nodedump').dump;
var app = express();

var host = 'www.lingkapis.com';
var apikey = '[yourkey]';
var secret = '[yoursecret]';

app.get('/', function (req, res) {
   var d = new Date();
   var requestPath = '/v1/@self/ps/studentauthentications';
   var requestMethod = "GET";
   var formattedDate = dateFormat(d,"GMT:ddd, dd mmm yyyy HH:MM:ss Z");
   var message = "date: "+ formattedDate + "\n(request-target): " + requestMethod.toLowerCase() + " " + requestPath;
   var hmacer = crypto.createHmac('sha1', secret);
   hmacer.write(message);
   hmacer.setEncoding('base64');
   hmacer.end();

   var sig = hmacer.read();  

   // options for API request
   var options = {
        host: host,
        path: requestPath,
        method: requestMethod,
        headers: {
            'Date': formattedDate, 
            'Authorization': 'Signature keyId="'+apikey+'",algorithm="hmac-sha1",headers="date (request-target)",signature="'+qs.escape(sig)+'"'
        }
   }


 // callback for API Call
   callback = function(response) {

    var body = ''
    response.on('data', function (chunk) {
        body += chunk;
    });

    response.on('end', function () {
        try {

                var parsed = JSON.parse(body);
                //capture dump 
                var output = nodedump(parsed);

                // write response to the browser 
                res.send(
                    '<html>'
                        + '<head>'
                            + '<title>Lingk API Example</title>'
                        + '</head>'
                        +'<body>'
                            +output
                        +'</body>'
                    +'</html>'
                );                
            } catch (err) {
                res.send('Unable to parse response as JSON', err.stack);
            }
        }).on('error', function(err) {
        // handle errors with the request itself
        res.send('Error with the request:', err.message);
        }); 
   }

   // API request
    var httpreq = https.request(options, callback);
    httpreq.end();

   console.log("date " + formattedDate);
   console.log("message " + message);
   console.log("secret " + secret);
   console.log("signature " + sig); 

});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});
```

Example output ![](https://github.com/lingk/dev/tree/1afeb15bd5d5a5c3dc78b70bdc93e1e0c89bddd1/code_samples/images/nodejs.png)


# PHP

```php
<?php 
$entrypoint = 'https://www.lingkapis.com';
$apikey = '[your key]';
$secret = '[your secret]';
$studentdata = servicecall('/v1/@self/ps/students', "", "GET");

function servicecall($service, $querystring, $method) {
    global $entrypoint, $apikey, $secret;
    $timestamp = gmdate('D, d M Y H:i:s \U\T\C', time());
    $message = "date: $timestamp\n(request-target): ".strtolower($method)." ".$service;
    $signature = base64_encode(hash_hmac('sha1', $message, $secret, true));

    $url = $entrypoint . $service  . $querystring;
    $dateheader = 'Date: '."$timestamp";  
    $authheader = 'Authorization: Signature keyId="'.$apikey.'",algorithm="hmac-sha1",headers="date (request-target)",signature="'.urlencode($signature).'"';   

    echo 'curl -n';
    echo " -H \"".$dateheader.'"'; 
    echo " -H \"".$authheader.'"';
    echo " ".$url;
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
       curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    $dateheader,      
    $authheader
    ));
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}
?>

<html>
 <head>
  <title>PHP Test</title>
 </head>
 <body>   
 <?php echo $studentdata; ?> 
 </body>
</html>
```


# Python

## Python 2

This example demonstrates an HTTP GET of Lingk event subscriptions.

```python
#!/usr/bin/env
import datetime
import httplib
import hashlib
import hmac
import base64
import urllib

# Constants
endPoint = "www.lingkapis.com"
keyId = "[Access Key]"
secret = "[Secret]"

def createSignature(secret, signingStr):
    """Creates signature for a signing string"""
    message = bytes(signingStr).encode('ascii')
    secret = bytes(secret).encode('ascii')
    signature = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha1).digest())
    return signature

def createAuthHeader(keyId, secret, dateStr):
    """Generates authorization header for a given key and secret"""
    requestPath = "/v1/@self/webhooks/subscriptions"
    requestMethod = "GET"
    signingStr = "date: " + dateStr +  "\n(request-target): " + requestMethod.lower() + " " + requestPath
    encodedHMAC = urllib.quote_plus(createSignature(secret, signingStr))
    return "Signature keyId=\"" + keyId + "\",algorithm=\"hmac-sha1\",headers=\"date (request-target)\",signature=\"" + encodedHMAC + "\""

def getHTTPResponse(endPoint, authorizationHeader, dateStr):
    """Connects to an endpoint via HTTPS and retrieves response"""
    connection = httplib.HTTPSConnection(endPoint)
    headers = {'Date': dateStr, 'Authorization': authorizationHeader}
    connection.request('GET', '/v1/@self/webhooks/subscriptions', headers=headers)
    response = connection.getresponse()
    return response

# Start here
dateStr = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S UTC")

authorizationHeader = createAuthHeader(keyId, secret, dateStr)
res = getHTTPResponse(endPoint, authorizationHeader, dateStr)

data = res.read()
print(data)
```

## Python 3

This example demonstrates an HTTP POST of Lingk events.

```python
import datetime
import http.client
import hashlib
import hmac
import base64
import urllib.parse
import json

# Constants
endPoint = "www.lingkapis.com"
keyId = "[Access Key]"
secret = "[Secret]"


# Test Data
contact = '''
    {
        "verb": "create",
        "eventObject": {
            "applicantId": "12345678",
            "studentId": "909706331",
            "title": "Mr",
            "firstName": "Joe",
            "middleName": "Joseph",
            "lastName": "Soap",
            "birthDate": "1996-07-19",
            "gender": "Male"
        },
        "objectType": "mytenant.mywork.contact"
    }'''

def createSignature(secret, signingStr):
    """Creates signature for a signing string"""
    message = signingStr.encode('ascii')
    secret = secret.encode('ascii')
    signature = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha1).digest())
    return signature

def createAuthHeader(keyId, secret, dateStr):
    """Generates authorization header for a given key and secret"""
    requestPath = "/v1/@self/events"
    requestMethod = "POST"
    signingStr = "date: " + dateStr +  "\n(request-target): " + requestMethod.lower() + " " + requestPath
    encodedHMAC = urllib.parse.quote_plus(createSignature(secret, signingStr))
    return "Signature keyId=\"" + keyId + "\",algorithm=\"hmac-sha1\",headers=\"date (request-target)\",signature=\"" + encodedHMAC + "\""

def getHTTPResponse(endPoint, authorizationHeader, dateStr, payLoad):
    """Connects to an endpoint via HTTPS and retrieves response"""
    connection = http.client.HTTPSConnection(endPoint)
    #connection.set_debuglevel(5)
    headers = {'Date': dateStr, 'Authorization': authorizationHeader}
    connection.request('POST', '/v1/@self/events', body=payLoad, headers=headers)
    response = connection.getresponse()
    #print(response.getheaders())
    return response

def triggerContactEvent():
    # Start here
    dateStr = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S UTC")
    #print(dateStr)
    authorizationHeader = createAuthHeader(keyId, secret, dateStr)
    res = getHTTPResponse(endPoint, authorizationHeader, dateStr, contact)

    success = False
    result = res.read()
    try:
        jsnResult = json.loads(result.decode("UTF-8"))
        if "eventGuid" in jsnResult:
            success = True
    except:
        pass

    return({"Date": dateStr, "AuthorizationHeader": authorizationHeader, "Success": success, "Result": result})

if __name__ == "__main__":
    result = triggerContactEvent()
    print(result)
```


# Salesforce Apex

This code executes the Lingk Event REST API and can pass data from Salesforce to a recipe based on a Salesforce event.

```java
String apiEndpoint = 'https://www.lingkapis.com/v1/@self/events';
String key = '[key]';
String secret ='[secret]'; 

String requestMethod = 'POST';

// Signature creation
System.Url URL = new Url(apiEndpoint);

// Date formatting for Signature and Date header

DateTime dt = DateTime.now();
Date localDate = dt.date();
Time localTime = dt.time();

DateTime nowInUtc = DateTime.newInstance(localDate, localTime);
String formattedDate = nowInUtc.formatGMT('EEE, dd MMM yyyy HH:mm:ss');
formattedDate = formattedDate+' GMT';
// formatGMT('EE, dd mmm yyyy HH:nn:ss GMT');
// "Mon, 02 Jan 2006 15:04:05 GMT"

system.debug('formatted date is ' + formattedDate);
system.debug('request method is ' + requestMethod.toLowerCase());
system.debug('url path  is ' + URL.getPath());

// create a message to sign
String message = 'date: ' + formattedDate + '\n(request-target): ' + requestMethod.toLowerCase() + ' ' + URL.getPath();

system.debug('signingString is ' + message);

Blob signatureBlob = Crypto.generateMac('HMacSHA1', Blob.valueOf(message), Blob.ValueOf(secret));

String signature = EncodingUtil.urlEncode(EncodingUtil.base64Encode(signatureBlob),'ASCII');

system.debug('signature is ' + signature);

// construct authorization header
String authorizationHeader = 'Signature keyId=\"' + key + '\",headers=\"date (request-target)\",algorithm=\"hmac-sha1\",signature=\"' + signature + '\"';

system.debug('authorizationHeader is ' + authorizationHeader);

// JSON Payload
JSONGenerator gen = JSON.createGenerator(true);
gen.writeStartObject();
gen.writeStringField('verb', 'create');
gen.writeStringField('objectType', 'centralu.salesforce.newlead');
gen.writeFieldName('eventObject');
    gen.writeStartObject();
    gen.writeEndObject();    
gen.writeEndObject();
String jsonBody = gen.getAsString();
System.debug(jsonBody); 

// HTTP Request
  // for the code the call the Lingk REST APIs
  // It is best to separate this into a separate class or method
  Http h = new Http();
  HttpRequest req = new HttpRequest();
  req.setHeader('Date', formattedDate);
  req.setHeader('Authorization', authorizationHeader);
  req.setEndpoint(apiEndpoint);
  req.setMethod(requestMethod);
  req.setBody(jsonBody);
  String responseBody;
  HttpResponse res;
  res = h.send(req);
  responseBody = res.getBody();
  system.debug('response ' + responseBody);

  // add additional debugging for HTTP code and errors
```


# API Explorer

To access the Swagger documentation for Lingk APIs, visit <http://apiexplorer.lingkapis.com/> .

![](/files/-M3s1iMdG5K7D4hGqNpP)


# API Plugin for Apache Nifi

## This documentation is archived.

All new implementation please use the following documentation link: <http://help.lingk.io/en/articles/128-on-premise-adapter-powered-by-apache-nifi-installation-guide>

## Lingk Adapter

The Lingk Adapter is used to connect on-premise SIS/ERP/databases with Lingk integrations solutions using secure REST APIs. The Lingk Adapter contains an API dashboard that supports configuration of desired data flow pipelines required for each data scenario. This document intends to contains complete information required to configure the Lingk Adapter.

This guide walks you through:

1. Installing the Lingk Adapter.
2. Configuring the Lingk Adapter security, network settings, and back-end access for internal configuration.
3. Configuring the Lingk Adapter to externally connect with the LingkSync Speed Wizard.
4. Configuring the APIs served by the Lingk Adapter.

By the end of this configuration you should have a Lingk Adapter API endpoint to provide to your application administrator to complete LingkSync configuration.

If you are a Colleague by Ellucian users, please visit [Colleague Implementation notes](https://github.com/lingk/dev/tree/1afeb15bd5d5a5c3dc78b70bdc93e1e0c89bddd1/adapter/colleague-implementation-notes.html).

### Table of Contents

### Intended Audience

Prior knowledge of Linux system administration is required to execute the procedures mentioned in this document. Docker knowledge is desired but not mandatory.

Document should be used by:

* Lingk Adapter system administrator
* Client IT and SIS personnel

### Architecture Overview

### Implementation Procedure

#### Pre-requisites

**Server Requirement**

Minimum system requirement for running the Lingk Adapter is as follows:

* 2 CPU Cores
* 4 GB Memory
* At least 10 GB of hard disk storage

**Operating System Requirement**

The Lingk Adapter is tested to be working on Ubuntu 16.04 (Xenial Xerus) or later and Windows 10 (Insider Edition), but should be able to work on any operating system as long as the Docker requirements are fulfilled.

***For Linux***

Lingk recommends to use a Linux based distribution (Ubuntu 16.x server) and proceeding sections have instructions with Ubuntu 16.04 as host operating system.

***For Windows***

For Windows installation, see our separate [Docker on Windows Installation Guide for Lingk Adapter](/lingk-adapter/lingk-on-windows-installation-guide).

For AWS environments, the Amazon Linux AMI is a recommended starting point.

**Docker Requirement**

The Lingk Adapter is built using Docker Server version 1.12.6 but should work on later releases as well.

**Browser Requirements**

Lingk Adapter management console has been tested on below browsers;

* Internet Explorer 9+
* Mozilla FireFox 24+
* Google Chrome 36+
* Safari 8

#### Installation Steps

Estimated Time: 15-20 minutes

Lingk uses Git and Amazon Web Services (AWS) to host adapter container repositories. Following is three step process to install Lingk Adapter on client premises.

When you have completed this step, you will be able to:

* Run the adapter in a local environment
* Run the SQL Dashboard
* Configure Banner SQL Queries

Additional SSL and DNS configuration is needed to:

* Run the adapter in a production environment
* Provide an public endpoint to your LingkSync app administrator

**Before you begin...**

**Git and Amazon Bucket Locations**

See below for information on where to download the Lingk Adapter installation components using Git and AWS.

**Firewall Ports**

Ports 22, 80, 8083, and 9000 need to be open internally to your network so that your users can configure and maintain the Lingk Adapter. Port 3000 needs to be opened externally to your users (for running LingkSync wizards) as well as Lingk's Transformer Engine. Lingk’s Transformer Engine runs on Amazon AWS (unless a hybrid configuration is used). Amazon hosts a list of IP ranges for their services here: <https://ip-ranges.amazonaws.com/ip-ranges.json>, requests from our transformer will come from the ranges listed for region: us-east-1 and service: ec2.

**Getting Started**

If you are installing on a Windows machine, see the separate document, [Docker on Windows Installation Guide](/lingk-adapter/lingk-on-windows-installation-guide) for information on installing Docker.

**Step 1 - Install Docker and docker-compose**

***For Linux***

Install docker as per operating system based instructions from here;

<https://docs.docker.com/engine/installation/#platform-support-matrix>

Verify that docker has been installed

```
# sudo docker info
```

To create additional ldap users, host machine should also have ldap-utils installed. For instance, below command should install ldap-utils on ubuntu flavors supporting aptitude

```
# sudo apt-get install ldap-utils
```

or

```
# sudo apt-get update

# sudo apt-get ldap-utils
```

***For Windows***

Please see the [Docker on Windows Installation Guide](/lingk-adapter/lingk-on-windows-installation-guide).

**Step 2 - Install Lingk Adapter**

***For Linux***&#x20;

As a prerequisite, install docker-compose and docker on a server where installation of LingkSync is desired. Afterwards, follow below steps to deploy the latest version of LingkSync.

**Copy and paste** the command below into your terminal to obtain the GIT repository for the Lingk Adapter installation process.

```
# git clone https://github.com/lingkio/lingkadapter-docker.git
```

After downloading the git repository, you will need to change permissions for the script

```
# cd /lingkadapter-docker
# chmod 755 adapter-install.sh
```

On Linux, during execution, your script will ask for the location of the LingkSync adapter file and the Lingk LDAP file.

Get the URLs for the latest versions from [Resources](https://app.lingk.io/resources) page in the Lingk console.

```
Enter path of Lingksync adapter file
[enter URL from Resources page for lingkadapter-lingksync Docker image]
Enter path of Lingk ldap file
[enter URL from Resources page for lingkadapter-ldap docker Docker image]
```

***For Windows***

As a prerequisite, install Docker for Windows on a Windows machine where installation of LingkSync is desired.

**Copy and paste** the command below into your terminal to obtain the GIT repository for the Lingk Adapter installation process.

```
# git clone https://github.com/lingkio/lingkadapter-docker.git
```

After downloading the git repository, you will need to change permissions for \_\_the script

```
# cd /lingkadapter-docker
# Give full control to adapter-install-docker-windows.bat
```

If installing on Windows, you will need to manually create a folder named "downloaded" within the lingkadapter-docker folder on your machine:

```
# mkdir downloaded
```

Now download the latest versions of the LingkSync adapter and Lingk LDAP files from the [Resources](https://app.lingk.io/resources) page in the Lingk console into the "downloaded" folder. Once downloaded, rename the files as:

```
# lingkldap.tar.gz
# lingksync.tar.gz
```

Execute the script

```
# ./adapter-install-docker-windows.bat
```

***All Platforms***

Verify your installation

```
# docker ps -a
```

**Step 3: Open Lingk Adapter in your Browser**

After completing above steps to install Lingk’s adapter, you can access it using a web browser from <https://YOURMACHINEIP:8083/lingk/>. In order to change the port, simply replace 8083 with the desired port. **NOTE:** If using Windows, you will access the REST Adapter on the local machine via <https://YOURDOCKERIP/lingk/> (see the [Docker on Windows Installation Guide](/lingk-adapter/lingk-on-windows-installation-guide) for details).

To login, use the default administrator credentials of:

```
username: admin
password: password
```

To change the password and/or create more users, see **Create Users in LDAP.**

| IMPORTANT: Reset the default Lingk Adapter Administrator password. Please see the instruction later in this document for Managing Users. |
| ---------------------------------------------------------------------------------------------------------------------------------------- |

#### Configuring Lingk APIs

**About the Lingk Adapter APIs**

Lingk Adapter APIs enable on-premise datasets to be available via RESTful APIs to wizards, recipes and connectors through the Lingk Transformer Engine. Lingk Adapter APIs can be configured using the API Dashboard that ships with the Lingk Adapter. On-premise dataset can be accessed by a SQL (i.e. query, view or store procedure) or delimited file. Each dataset is provided an API endpoint by the Lingk Adapter. Additionally, some on-premise systems may have unique data access requirements.

Lingk Adapter APIs are secured between the Lingk Adapter and Lingk Transformer Engine using JWT security and provide per message signatures.

Lingk Adapter APIs are used solely for the purposes of the Lingk connectors and the Lingk Platform. Enabling these APIs to be reused for other purposes in the planning stages. Please submit a request to <support@lingk.io>, if you have business or technical requirements that could reuse these APIs.

**Constructing Data Sets from Queries, Flat-files and Operations**

Traditionally, in point to point integration, how you build datasets and the names of the output columns is not that important. With the Lingk Adapter, there are benefits to considering the names and values of the data that is returned.

**To improve the integration experience, Speed Wizards can provide automapping for commonly named fields.**&#x20;

With a small amount of effort you can take common attributes in your datasets and enable them to be automatically aligned to Salesforce objects by aligning names and values with the list below. Additionally, your datasets will be easier to understand in future integrations.

Default Speed Wizard mapping with Salesforce by resource and field name:

| Lingk Adapter API Name | Field Name  | LingkSync Salesforce Mapping                     |
| ---------------------- | ----------- | ------------------------------------------------ |
| Applicants             | ExternalId  | Contact -> Lingk External Id                     |
| Applicants             | FirstName   | Contact -> First Name                            |
| Applicants             | LastName    | Contact -> Last Name                             |
| Applicants             | Gender      | Contact -> Gender                                |
| Institutions           | Name        | Account: Educational Institution -> Account Name |
| Departments            | Name        | Account: University Department -> Account Name   |
| Courses                | ExternalId  | Course -> Lingk External Id                      |
| Courses                | Description | Course -> Description                            |
| Courses                | Credits     | Course -> Credit Hours                           |
| Courses                | CourseTitle | Course -> Course Name                            |
| Terms                  | ExternalId  | Term -> Lingk External Id                        |
| Terms                  | Name        | Term -> Term Name                                |
| Course Sections        | ExternalId  | Course Offering -> Lingk External Id             |
| Course Sections        | StartDate   | Course Offering -> Start Date                    |
| Course Sections        | EndDate     | Course Offering -> End Date                      |
| Course Sections        | Capacity    | Course Offering -> Capacity                      |

Unlike other tools, this isn’t about just source and target name matching. LingkSync recognizes fields that have names and values associated with the Common Education Data Standard (CEDS). You can learn more about CEDS at <https://ceds.ed.gov>.

**Configuration Options for Data Backends for APIs**

The Lingk Adapter has 2 modes of operation:

1. Direct SQL queries: for on-demand querying against live data
2. Flat file queries: for on-demand querying against file-based snapshots

Under both of these modes, the data is accessible via REST APIs to the LingkSync app. Flat file query mode would remove any impact on SIS operations and utilize a file to be dropped to a director available to a local on-premise environment.

|                                | Direct SQL Query Operation | Flat File Query Operation |
| ------------------------------ | -------------------------- | ------------------------- |
| Lingk API Secret Configuration | Yes                        | Yes                       |
| On-Demand Database Querying    | Yes                        | No                        |
| On-Demand File Querying        | No                         | Yes                       |
| API Tool Configuration         | SQL                        | File                      |

**One-time API Configurations**

Complete all the one-time configurations before configuring individual APIs.

**Getting Your Lingk Adapter API Credentials**

You will receive your Lingk Adapter API secret from the provider configuration under Environments in your workspaces.

**Configuring Your Data Flow**

**1. Download the Lingk Adapter Flow template for your database from the** [**Resources**](https://app.lingk.io/resources) **page.**&#x20;

**2. Upload the template XML file using the upload template button on the "Operate" window.** ![](/files/-M3s1ipJ5_MD29DYwl02)

**3. Drop a "Process Group" on the main screen and name it.** ![](/files/-M3s1ipLy1u8f2sblkuP)

**4. Double-click into the process group**

**5. Drop the template for your database onto the screen** For the current list of available templates on the Resources in the Lingk console. ![](/files/-M3s1ipOArtKgt_i7T0H)

Note: All database templates support flat files endpoints.

**6. Configure your controller services** Click on the gear for your processor group. A. Enable HTTP Services

B. Enable HTTPS Services Use `lingkadapter123` as the password for TrustStore and KeyStore

C. Counfigre the database connection.

**7. Start all processors in the flow**

![image alt text](/files/-M3s1ipQPO0_g9jPEFpw)

**Database Connections**

If you are configuring the Lingk Adapter to connect directly to your database using queries, views or stored procedures, you will need to configure your database connections.

Configure your database driver

* Oracle (for PeopleSoft Campus Solutions and Banner by Ellucian)
* Postgres
* Microsoft SQL Server
* Other JDBC providers

**Configuring Database Connectivity**

Here are the instructions for an Oracle database.

**1. Open the Main Flow configuration dialog as shown below.**

**2. Click on the pencil bolt icon for DBCPConnectionPool Controller Service**

Note: You may also need to enable the `StandardHttpContextMapPort3000` controller service, if it is disabled.

**3. Enter your JDBC connection string, username and password into the Lingk Adapter processor** **Oracle Connection Strings** *Example Oracle connection strings (Service Name)*

```
jdbc:oracle:thin:scott/tiger@//myhost:1521/myservicename
jdbc:oracle:thin:@//myhost:1521/myservicename
jdbc:oracle:thin:@myhost:1521/myservicename
```

*Example connection string (SID)\_*

```
jdbc:oracle:thin:@myhost:1521:ORCL
```

For more information on Oracle Connection strings go to: <https://docs.oracle.com/cd/B28359_01/java.111/b31224/jdbcthin.htm>

**Postgres Connection Strings**

**Microsoft SQL Server Connection Strings**

*Example connection string (database user)*

```
jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks
```

*Example connection string (domain user)*

```
jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks
;integratedSecurity=true
```

The Database Driver location should be preloaded with the JDBC driver driver associated with your flow template. ![image alt text](/files/-M3s1ipSMRKoOW_3fuOE)

**4. Click Apply.**

**5. Click on the Lightning icon for the same Controller service**

**6. Enable the Controller Service to load the changes into associated processors**

![image alt text](/files/-M3s1ipUq7x4vHvxV8qS)

**7. Press the "Play" button on the main flow to start all configured processors in the flow.**

Now you are able to start configuring which datasets you want to expose through the Lingk Adapter.

**Configuring An API with an SQL Database Query Backend**

We will walk through the configuration of the Lingk Adapter for managing APIs used in the LingkSync product.

Open the API dashboard in separate browser tab with the following URL (if configured for Private IP) `https://<machineip|domain>:9000/dashboard/`

Login using your Lingk Adapter credentials

\[add login screen for dashboard]

**Delete** any preset APIs from the list that do not align to your data scenario and **add** any APIs to the list to align to your data scenario.

![image alt text](/files/-M3s1ipWEUJBBedfDG0e)

Test any SQL queries and views using the API Dashboard to generate API metadata. Use the GET HTTP verb for pulling data from Oracle. See the section titled "Constructing Data Sets from Queries, Flat-files and Operations" on constructing user-friendly and reusable endpoints.

![image alt text](/files/-M3s1ipYmfCl90CEdgNr)

Once all endpoints are complete, configure the API endpoint in the Lingk HQ. Find your provider under your workspace's environment navigation.

**Configure API Security for Lingk Transformer Engine**

**1. Stop the Client Secret processor to enable updates**

![image alt text](/files/-M3s1ip_4mfSi9A-nODw)

**2. Click Configure on the processor**

**3. Create a new Property in the Lingk Adapter processor for your LingkSync using the \[+] icon.**&#x20;

![image alt text](/files/-M3s1ipbJfCutuv9PstD)

**4. Enter your Lingk API Secret into the Lingk Adapter processor.**

**5. Apply Changes**

![image alt text](/files/-M3s1ipdT6TS6ZAWwA-I)

**6. Restart the Client Secret processor**

![image alt text](/files/-M3s1ipfuTMpWQSYmSFx)

**Configuring An API with a File-based Query Backend**

Open the API dashboard in separate browser tab with the following URL (if configured for Private IP) `https://<machineip|domain>:9000/dashboard/`

Create a new API and specify the Endpoint Type of "File"

**Default File Drop Directory**

The default directory for storing CSV files to serve as an API is /opt/nifi-1.x.x/extras/files/csv. You can upload a CSV file onto your Lingk Adapter instance with the following docker command:

`docker cp yourFile.csv {DOCKER_TAG}:/opt/nifi-1.x.x/extras/files/csv/yourFile.csv`

You can target the output of scheduled processes to this directory and keep the file name the same to keep your files updated.

For network drive support, please contact Lingk support at <support@lingk.io> .

**Adding a File-based API Endpoint**

Specify the names, files and fields using the File Tool to configure the API endpoint. See the section titled "Constructing Data Sets from Queries, Flat-files and Operations" on constructing user-friendly and reusable endpoints.

Test your file-based endpoint with "Preview Data"

![image alt text](/files/-M3s1iphA06UojeeGfdX)

Click "Save" when you are finished with the endpoint

#### Additional Installation Options

**Manage Users in LDAP**

| IMPORTANT: Reset both the default Administrator password. |
| --------------------------------------------------------- |

By default a user ‘admin (password: password)’ has been assigned administrator privileges and created automatically in Lingk ldap. Admin user can assign rights to more users that are created in Lingk’s LDAP.

In order to create more users in Lingk LDAP, update the file named *conf/allinone.ldif* in the lingkadapter-ldap docker container with below data for each user replacing `<username>`, `<username@lingk.io>` and `<anypassword>` fields with relevant information. Ideally any two users records should be separated by a blank line.

```
dn: cn=username,ou=people,dc=lingk,dc=io
objectclass: inetOrgPerson
objectclass: organizationalPerson
objectclass: person
objectclass: top
cn: username
description: A New User
sn: username
uid: username
mail: [username@lingk.io](mailto:username@lingk.io)
userpassword: anypassword
```

once the file is saved on disk, below command should create all users mentioned in ldif file;

`ldapadd -h localhost -p 389 -c -x -D cn=admin,dc=lingk,dc=io -W -f users.ldif`

In order to change password for a user (for instance below procedure can be applied to change admin password)

ldappasswd -h localhost -p 389 -x -D "cn=admin,ou=people,dc=lingk,dc=io" -W -A -S

ldap server asks response to following prompts;

```
Old password: <existing password: password>
Re-enter old password:
New password: <new password:>
Re-enter new password:
Enter LDAP Password: <master slapd password configured at the time of installation: default are ‘password’ or ‘mysecretpassword’>
```

**SSL Certificate Generation**

Note: This step is not required if SSL Private Mode is followed. For SSL Dedicated Domain Mode, this section is mandatory.

Lingk's recommended approach is to use certificates from a trusted Certificate Authority (CA) and Lingk uses [http://www.letsencrypt.org](http://www.letsencrypt.org/) as CA. Below procedure is applicable if client intends to use their own certificates.

Lets assume that client wants to generate certificates for subdomain adapter.yourwebsitedomain.com to host lingkadapter. Following are a set of steps that need to be carried out for certificate generation.

**Step 1: Copy utility script repository on server that is hosting the domain/subdomain**

`curl --silent [https://raw.githubusercontent.com/srvrco/getssl/master/getssl](https://raw.githubusercontent.com/srvrco/getssl/master/getssl) > getssl ;`

`chmod 700 getssl`

**Step 2: Generate certificate configuration files**

`./getssl -c adapter.yourwebsitedomain.com`

**Step 3: Edit the getssl.cfg file**

Changed the ACCOUNT\_EMAIL directive to actual e-mail address

`/root/.getssl/getssl.cfg`

Change Server location from ‘staging’ to production or else it will not be trusted

The staging server is best for testing (hence set as default) CA="[https://acme-staging.api.letsencrypt.org](https://acme-staging.api.letsencrypt.org/)"

This server issues full certificates, however has rate limits CA="[https://acme-v01.api.letsencrypt.org](https://acme-v01.api.letsencrypt.org/)"

**Step 3a: Edit the getssl.cfg file for the domain**

Comment the SANS= directive, because we will have exactly 1 domain.

/root/.getssl/yourwebsitedomain.com/getssl.cfg

Specify ACL directive to:

ACL=('/some/directoryAsRoot/.well-known/acme-challenge')

Un-comment following directives:

```
DOMAIN_CERT_LOCATION="/etc/ssl/adapter.yourwebsitedomain.com.crt"
DOMAIN_KEY_LOCATION="/etc/ssl/adapter.yourwebsitedomain.com.key"
CA_CERT_LOCATION="/etc/ssl/chain.crt"

DOMAIN_CHAIN_LOCATION="/etc/ssl/" # this is the domain cert and CA cert
DOMAIN_PEM_LOCATION="/etc/ssl/" # this is the domain_key, domain cert and CA cert
```

Save this file.

**Step 4: Start a Small Server Thread (Temporary)**

Create a separate terminal window for this step to start a process that will be used for subsequent steps.

For ACL generation, we start a small server that letsencrypt will use to verify whether we are on the server hosting the lingkadapter

```
$ mkdir /some
$ mkdir /some/directoryAsRoot
$ mkdir /some/directoryAsRoot/.well-known
$ cd /some/directoryAsRoot
$ python -m SimpleHTTPServer 80
```

Note: If python is not found, in ubuntu you can install python-minimal using below command;

$ apt install python-minimal

Once this has been started, let this command run on this terminal and continue from next step in another terminal window.

**Step 5: Generate SSL Certificates for the domain**

Run the following command to generate SSL Certificates for the domain.

`$ getssl adapter.yourwebsitedomain.com`

Certificates will be generated in location /etc/ssl/. Following two files will be of significance

`adapter.yourwebsitedomain.com_chain.pem`

`adapter.yourwebsitedomain.com.pem`

**Step 6: Stop Small Server Thread**

Once Step 5 is executed successful and certificates have been verified, open the terminal that was left in Step 4 and press 'Ctrl+c' to terminate the server thread. This terminal window is no longer needed.

**Step 7: Generate JAVA Keystore and formats as desired by Adapter**

Lingk Adapter has embedded Java-based server that needs Java Keystore (JKS). In order to generate keystore.jks from the generated certificates in the above steps 1-6, follow these two steps.

$ keytool -import -file adapter.yourwebsitedomain.com\_chain.pem -alias cacert -keystore truststore.jks -storepass lingkadapter123

Convert the key to pkcs12 format.

$ openssl pkcs12 -export -inkey adapter.yourwebsitedomain.com.pem -in adapter.yourwebsitedomain.com.pem -out cert\_key.p12 use password: lingkadapter123

The two generated files (i.e. keystore.jks and cert\_key.p12) can be used as newly generated certificates. Place these two files under folder ‘certs’.

**Changing the Default Mapped Ports**

When you are in an environment that has limited external ports, you may need to change the ports mapped in your Lingk Adapter. The instructions below will help you map the API port to the standard HTTPS port. You still may need to use localhost on the server for all configuration of the APIs with the solution below.

There are two solutions to exposing your API port (port 3000) externally on a default port (443) and you can choose which one is most expedient for you.

A. Delete the Lingk Adapter container and recreate it with this command:

```
# docker run \
-idt \
-p 8083:443 \
-p 9000:9000 \
-p 443:3000 \
--link lingkldap:lingk \
--name lingkadapter-lingksync \
quay.io/lingkio/lingkadapter-lingksync:latest
```

B. Modify the ports on your existing Lingk Adapter container per the following:

<https://mybrainimage.wordpress.com/2017/02/05/docker-change-port-mapping-for-an-existing-container/>

**Event Based Integration Lingk Adapter to the Lingk Platform**

Refer to the "Lingk Adapter for Event-Based Integration Guide"

**Upgrade the LingkSync Adapter to the Latest Version**

Refer to the "Lingk Adapter Upgrade Guide"

#### Resources

**The Lingk Adapter Distribution of Apache Nifi**

The Lingk Adapter is a higher education distribution of the Apache Nifi project. If you would like to learn more about Apache Nifi concepts please go to:

<https://nifi.apache.org/docs/nifi-docs/html/overview.html>

![image alt text](/files/-M3s1ipjSo3y3Q1moqLN)


# Docker on Windows Installation Guide

## This documentation is archived.

All new implementation please use the following documentation link: <https://help.lingk.io/en/articles/128-on-premise-adapter-powered-by-apache-nifi-installation-guide>

## Docker on Windows Installation Guide for Lingk REST Adapter

This guide will walk you through preparing your Windows installation for a Docker installation that will support the installation of the Lingk Adapter on Windows. For this install, you will need to install and/or download:

1. Windows 10 Insider Edition
2. Docker for Windows
3. Download the Lingk Rest Adapter installation files

### Windows 10 Insider Edition

**Windows Insider** is an open software testing program by Microsoft that allows users who already own a valid license of **Windows 10** or **Windows Server 2016** to sign up for pre-release builds of the operating system previously only accessible to developers (<https://insider.windows.com/en-us/getting-started/>). The current Windows Insider version is needed to ensure the proper functioning of Linux-based Docker commands within Windows Docker containers.

### Docker for Windows

To install Docker for Windows, visit <https://www.docker.com/get-docker> and download the version of your choice (CE or Enterprise).

Once downloaded, run the installer and follow the on-screen prompts. When prompted to enable features such as Hyper-V, you must select Yes to ensure proper execution of the Lingk REST Adapter containers.

After the installation has completed, you will see an icon on the desktop for Docker for Windows. Double-click it.

![](/files/-M3s1iUSmtSiQeX3cd8p)

Double click this to set up Docker as a service on your machine.

Once Docker is installed and running (check for the gray Docker icon in the System Tray). NOTE: It may take a few minutes for the Docker service to start.

![](/files/-M3s1iUU8D0SGU-vTKW6)

Test your Docker installation by opening PowerShell and trying:

| > docker ps |
| ----------- |

You should also be able to test Docker containers by running Hello World.

| > docker run hello-world |
| ------------------------ |

![](/files/-M3s1iUWM_LpfDcQFi3H)

Open the Docker settings by right-clicking the Docker icon in the System Tray. You will see a screen that looks like the following:![](/files/-M3s1iUYn6Hrg9UOyCxa)You will have to edit the Network and Proxies settings within Docker to enable Local Machine access to the Lingk REST Adapter once it installed.

First, click on Network, where you should see a screen such as the following:![](/files/-M3s1iU_AWy_P_4QIKyb)Ensure that you have supplied a subnet address and subnet mask value. The defaults (such as above) are sufficient.

Next, click Proxies. You will need to supply an IP address for HTTP and HTTPS. The default values (shown below) are sufficient.![](/files/-M3s1iUbQwHGt9nMy59u)These settings must be in place to get around an issue where Windows' port forwarding to Docker containers does not work properly when trying to view Web output on the machine Docker is installed on.

You are now ready to return to the main [Lingk REST Adapter](/lingk-adapter) document and install the Lingk REST Adapter. Once the REST Adapter is installed, you will be able to test your installation via the IP below where you should see a screen that looks like the one below:

![](/files/-M3s1iUdAibk9n1TG8zR)

.


# Colleague Implementation Notes

## This documentation is archived.

All new implementation please use the following documentation link: <https://help.lingk.io/en/articles/142-nificonnect-for-colleague-by-ellucian>

## Lingk Adapter templates for Colleague

The Lingk Adapter can be configured for Colleague by Ellucian support by working with Lingk support (<support@lingk.io>).

### API Tool for Colleague

The Lingk Adapter template for Colleague enables institutions to define API endpoints with Subroutines and Saved list. Once the API is defined, you can use the API in recipes to orchestrate data integration and aggregration.

### Architecture

![](/files/-M3s1g1b6HZE57__5DiX)

The architecture of the Lingk Adapter for Colleague represents a unified way to access data from Unidata or Microsoft SQL Server-based implementations of Colleague.

The Lingk Adapter uses the Unidata Java SDK to execute subroutines in Colleague programmatically.

Lingk provides a set of Unidata subroutines to help you get started and can help write additional ones as part of your implementation.

### A Note on Ellucian Ethos

Ellucian Ethos solves the problem of unified data access across Unidata or Microsoft SQL Server-based implementations of Colleague. If you have Ellucian Ethos, you don't need an adapter installed or subroutines created. As a leader in Ellucian Ethos integration, Lingk's recipes enable you to more easily work with Ethos APIs than other tools.


# Testing the API Plugin with Postman


