NEW

Attend SmartCon 2023 to explore the future of Web3. Sign up now.

API Calls: Using Any API

In this guide, you will learn how to request data from a public API in a smart contract. This includes understanding what Tasks and External adapters are and how Oracle Jobs use them. You will also learn how to find the Oracle Jobs and Tasks for your contract and how to request data from an Oracle Job.

How does the request and receive cycle work for API calls?

The request and receive cycle describes how a smart contract requests data from an oracle and receives the response in a separate transaction. If you need a refresher, check out the Basic Request Model.

For contracts that use Chainlink VRF, you request randomness from a VRF oracle and then await the response. The fulfillment function is already given to us from the VRFConsumerBase contract, so oracles already know where to send the response to. However, with API calls, the contract itself defines which function it wants to receive the response to.

Before creating any code, you should understand how Oracle jobs can get data on-chain.

What are jobs?

Chainlink nodes require Jobs to do anything useful. In the case of a Request and Receive job, the Direct Request job monitors the blockchain for a request from a smart contract. Once it catches a request, it runs the tasks (both core and external adapters) that the job is configured to run and eventually returns the response to the requesting contract.

What are tasks?

Each oracle job has a configured set of tasks it must complete when it is run. These tasks are split into two subcategories:

  • Tasks - These are tasks that come built-in to each node. (examples: http, ethabidecode, etc).
  • External Adapters - These are custom adapters built by node operators and community members, which perform specific tasks like calling a particular endpoint with a specific set of parameters (like authentication secrets that shouldn't be publicly visible).

Tasks

If a job needs to make a GET request to an API, find a specific unsigned integer field in a JSON response, then submit that back to the requesting contract, it would need a job containing the following Tasks:

  • HTTP calls the API. the method must be set to GET.
  • JSON Parse parses the JSON and extracts a value at a given keypath.
  • Multiply multiplies the input by a multiplier. Used to remove the decimals.
  • ETH ABI Encode converts the data to a bytes payload according to ETH ABI encoding.
  • ETH Tx submits the transaction to the chain, completing the cycle.

The job specs example can be found here. Let's walk through a real example, where you will retrieve 24 volumes of the ETH/USD pair from the cryptocompare API.

  1. HTTP calls the API and returns the body of an HTTP GET result for ETH/USD pair. Example:
{"RAW":
  {"ETH":
    {"USD":
      {
        ...,
        "VOLUMEDAYTO":953806939.7194247,
        "VOLUME24HOUR":703946.0675653099,
        "VOLUME24HOURTO":1265826345.488568
        ...,
      }
    }
  }
}
  1. JSON Parse walks a specified path ("RAW,ETH,USD,VOLUME24HOUR") and returns the value found at that result. Example: 703946.0675653099

  2. Multiply parses the input into a float and multiplies it by the 10^18. Example: 703946067565309900000000

  3. ETH ABI Encode formats the input into an integer and then converts it into Solidity's uint256 format. Example: 0xc618a1e4

  4. ETH Tx takes the given input, places it into the data field of the transaction, signs a transaction, and broadcasts it to the network. Example: transaction result

Note: Some tasks accept parameters to be passed to them to inform them how to run. Example: JSON Parse accepts a path parameter which informs the task where to find the data in the JSON object.

Let's see what this looks like in a contract:

Contract example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */

/**
 * THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
 * THIS EXAMPLE USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract APIConsumer is ChainlinkClient, ConfirmedOwner {
    using Chainlink for Chainlink.Request;

    uint256 public volume;
    bytes32 private jobId;
    uint256 private fee;

    event RequestVolume(bytes32 indexed requestId, uint256 volume);

    /**
     * @notice Initialize the link token and target oracle
     *
     * Sepolia Testnet details:
     * Link Token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
     * Oracle: 0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD (Chainlink DevRel)
     * jobId: ca98366cc7314957b8c012c72f05aeeb
     *
     */
    constructor() ConfirmedOwner(msg.sender) {
        setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789);
        setChainlinkOracle(0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD);
        jobId = "ca98366cc7314957b8c012c72f05aeeb";
        fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varies by network and job)
    }

    /**
     * Create a Chainlink request to retrieve API response, find the target
     * data, then multiply by 1000000000000000000 (to remove decimal places from data).
     */
    function requestVolumeData() public returns (bytes32 requestId) {
        Chainlink.Request memory req = buildChainlinkRequest(
            jobId,
            address(this),
            this.fulfill.selector
        );

        // Set the URL to perform the GET request on
        req.add(
            "get",
            "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD"
        );

        // Set the path to find the desired data in the API response, where the response format is:
        // {"RAW":
        //   {"ETH":
        //    {"USD":
        //     {
        //      "VOLUME24HOUR": xxx.xxx,
        //     }
        //    }
        //   }
        //  }
        // request.add("path", "RAW.ETH.USD.VOLUME24HOUR"); // Chainlink nodes prior to 1.0.0 support this format
        req.add("path", "RAW,ETH,USD,VOLUME24HOUR"); // Chainlink nodes 1.0.0 and later support this format

        // Multiply the result by 1000000000000000000 to remove decimals
        int256 timesAmount = 10 ** 18;
        req.addInt("times", timesAmount);

        // Sends the request
        return sendChainlinkRequest(req, fee);
    }

    /**
     * Receive the response in the form of uint256
     */
    function fulfill(
        bytes32 _requestId,
        uint256 _volume
    ) public recordChainlinkFulfillment(_requestId) {
        emit RequestVolume(_requestId, _volume);
        volume = _volume;
    }

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }
}

Here is a breakdown of each component of this contract:

  1. Constructor: This sets up the contract with the Oracle address, Job ID, and LINK fee that the oracle charges for the job.
  2. requestVolumeData functions: This builds and sends a request - which includes the fulfillment functions selector - to the oracle. Notice how it adds the get, path and times parameters. These are read by the Tasks in the job to perform correctly. get is used by HTTP, path is used by JSON Parse and times is used by Multiply.
  3. fulfill function: This is where the result is sent upon the Oracle Job's completion.

Note: The calling contract should own enough LINK to pay the fee, which by default is 0.1 LINK. You can use this tutorial to learn how to fund your contract.

This is an example of a basic HTTP GET request. However, it requires defining the API URL directly in the smart contract. This can, in fact, be extracted and configured on the Job level inside the Oracle node. You can follow the APIConsumer tutorial here.

External adapters

If all the parameters are defined within the Oracle job, the only things a smart contract needs to define to consume are:

  • JobId
  • Oracle address
  • LINK fee
  • Fulfillment function

This will make your smart contract much more succinct. The requestVolumeData function from the code example above would look more like this:

function requestVolumeData() public returns (bytes32 requestId) {
    Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);

    // Extra parameters don't need to be defined here because they are already defined in the job

    return sendChainlinkRequest(req, fee);
}

You can follow a full Existing Job Tutorial here. More on External Adapters can be found here.

Further reading

To learn more about connecting smart contracts to external APIs, read our blog posts:

To explore more applications of external API requests, check out our other tutorials.

What's next

Stay updated on the latest Chainlink news