Chainlink Developers

Welcome to the Chainlink documentation site. You'll find comprehensive guides and documentation to help you start working with Chainlink as quickly as possible, as well as support if you get stuck. Click here for an introductory walkthrough on how to create a Chainlink request on the Ropsten test network!

Developers

Developers of external adapters will need to know how the Chainlink node requests data from it, and how the data should be formatted for a response. External adapters can be written in any language, and even ran on separate machines, to include serverless functions.

Requesting Data

When an external adapter receives a request from the Chainlink node, the JSON payload will include an id and a data object.

{"id":"278c97ffadb54a5bbb93cfec5f7b5503","data":{}}

If the node has a value defined for the Bridge Response URL, the payload will include a "responseURL" field that can be used to update responses via PATCH requests:

{
  "id": "278c97ffadb54a5bbb93cfec5f7b5503",
  "data": {},
  "responseURL": "http://localhost:6688/v2/runs/278c97ffadb54a5bbb93cfec5f7b5503"
}

Additional data may be specified in the spec and per run to be utilized by the adapter. This can be useful for requesting data from a REST endpoint where the keys and values can be specified by the requester. For example, if the REST endpoint supports the following:

https://example.com/api/:parent/:child

Then the payload to the external adapter would need:

{
  "id": "278c97ffadb54a5bbb93cfec5f7b5503",
  "data": {
    "parent": "myParentValue",
    "child": "myChildValue"
  }
}

The values for :parent and :child can be used within the adapter to dynamically build the URL for the request. This same concept can also be applied to URLs with query values. For example:

https://example.com/api/?parent=myParentValue&child=myChildValue

Returning Data

When the external adapter has a response payload, it will need to include it with the given jobRunID back to the node. In the adapter itself, this is easily accomplished by mapping the value for the given id to a new field in the return data called jobRunID.

An example of the response data can look like:

{
  "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503",
  "data": {
    "symbol": "ETH-USD",
    "last": {
      "price": 467.85,
      "size": 0.01816561,
      "timestamp": 1528926483463
    }
  },
  "status": "completed",
  "error": null,
  "pending": false
}

You'll also notice some additional fields: status, error, and pending. An external adapter may mark the JobRun as pending if the answer needs to be returned at a specified time, or when a desired result is found. The pending field should also be set to true if this is the case. When the external adapter calls back to the node to update the JobRun, this should be done with an HTTP PATCH request, see the Chainlink Node API documentation page for details.

Returning Errors

If the endpoint gave a known error, it would be best to include that in the external adapter's response back to the Chainlink node.

An example of what the error response payload can look like:

{
  "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503",
  "data": {},
  "status": "errored",
  "error": "The endpoint is under maintenance.",
  "pending": false
}

When an external adapter returns an error, the next task in the Job Spec is not executed.

Example

Here is a complete example of a simple external adapter written as a serverless function. It takes two input fields, inserts the API key as a header, and returns the resulting payload to the node.

let request = require('request');

exports.myExternalAdapter = (req, res) => {
  const url = "https://some-api.example.com/api";
  const coin = req.body.data.coin || "";
  const market = req.body.data.market || "";
  let requestObj = {
    coin: coin,
    market: market
  };
  let headerObj = {
    "API_KEY": "abcd-efgh-ijkl-mnop-qrst-uvwy"
  };
  let options = {
      url: url,
      headers: headerObj,
      qs: requestObj,
      json: true
  };

  request(options, (error, response, body) => {
    if (error || response.statusCode >= 400) {
        let errorData = {
            jobRunID: req.body.id,
            status: "errored",
            error: body
        };
        res.status(response.statusCode).send(errorData);
    } else {
      let returnData = {
        jobRunID: req.body.id,
        data: body
      };
      res.status(response.statusCode).send(returnData);
    }
  });
};

If given "ETH" as the value for coin and "USD" as the value for market, this external adapter will build the following URL for the request:

https://some-api.example.com/api?coin=ETH&market=USD

The headers in this case would include the API key, but that could just as easily be added to the requestObj if an API requires the key in the URL.