Part 2: Fetching Offchain Data

In Part 1, you successfully built and ran a minimal workflow. Now, it's time to connect it to the outside world. In this section, you will modify your workflow to fetch data from a public API using the CRE SDK's HTTPClient.

What you'll do

  • Add a new URL to your workflow's config file.
  • Learn about the runInNodeMode helper for offchain operations.
  • Write a new function to fetch data from the public api.mathjs.org API.
  • Integrate the offchain data into your main workflow logic.

Step 1: Update your configuration

First, you need to add the API endpoint to your workflow's configuration. This allows you to easily change the URL without modifying your TypeScript code.

Open the config.staging.json file in your my-calculator-workflow directory and add the apiUrl key. Your file should now look like this:

{
  "schedule": "*/30 * * * * *",
  "apiUrl": "https://api.mathjs.org/v4/?expr=randomInt(1,101)"
}

This URL calls the public mathjs.org API and uses its randomInt(min, max) function to return a random integer between 1 and 100. Note that the upper bound is exclusive, so we use 101 to get values up to 100. The API returns the number as a raw string in the response body.

Step 2: Understand the runInNodeMode pattern

Many offchain data sources are non-deterministic, meaning different nodes calling the same API might get slightly different answers due to timing, load balancing, or other factors. The api.mathjs.org API with the randomInt function is a perfect example—each call will return a different random number.

The CRE SDK solves this with runtime.runInNodeMode, a helper function that transforms these potentially varied results into a single, highly reliable result. It works like a "map-reduce" for the DON:

  1. Map: You provide a function (e.g., fetchMathResult) that will be executed by every node in the DON independently. Each node "maps" the offchain world by fetching its own version of the data.
  2. Reduce: You provide a consensus algorithm (e.g., consensusMedianAggregation) that takes all the individual results and "reduces" them into a single, trusted outcome.

This pattern is fundamental to securely and reliably bringing offchain data into your workflow.

Step 3: Add the HTTP fetch logic

Now, let's modify your main.ts file. You will add a new function, fetchMathResult, that contains the logic for calling the API. You'll also update the onCronTrigger function to call the runInNodeMode helper.

Replace the entire content of onchain-calculator/my-calculator-workflow/main.ts with the following code.

onchain-calculator/my-calculator-workflow/main.ts
Typescript
1 import { cre, consensusMedianAggregation, Runner, type NodeRuntime, type Runtime } from "@chainlink/cre-sdk"
2
3 type Config = {
4 schedule: string
5 apiUrl: string
6 }
7
8 type MyResult = {
9 result: bigint
10 }
11
12 const initWorkflow = (config: Config) => {
13 const cron = new cre.capabilities.CronCapability()
14
15 return [cre.handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)]
16 }
17
18 // fetchMathResult is the function passed to the runInNodeMode helper.
19 // It contains the logic for making the request and parsing the response.
20 const fetchMathResult = (nodeRuntime: NodeRuntime<Config>): bigint => {
21 const httpClient = new cre.capabilities.HTTPClient()
22
23 const req = {
24 url: nodeRuntime.config.apiUrl,
25 method: "GET" as const,
26 }
27
28 // Send the request using the HTTP client
29 const resp = httpClient.sendRequest(nodeRuntime, req).result()
30
31 // The mathjs.org API returns the result as a raw string in the body.
32 // We need to parse it into a bigint.
33 const bodyText = new TextDecoder().decode(resp.body)
34 const val = BigInt(bodyText.trim())
35
36 return val
37 }
38
39 const onCronTrigger = (runtime: Runtime<Config>): MyResult => {
40 runtime.log("Hello, Calculator! Workflow triggered.")
41 // Use runInNodeMode to execute the offchain fetch.
42 // The API returns a random number, so each node can get a different result.
43 // We use median consensus to find a single, trusted value.
44 const result = runtime.runInNodeMode(fetchMathResult, consensusMedianAggregation())().result()
45
46 runtime.log(`Successfully fetched and aggregated math result: ${result}`)
47
48 return {
49 result,
50 }
51 }
52
53 export async function main() {
54 const runner = await Runner.newRunner<Config>()
55 await runner.run(initWorkflow)
56 }
57
58 main()
59

Step 4: Run the simulation and review the output

Run the simulate command from your project root directory (the onchain-calculator/ folder). Because there is only one trigger, the simulator runs it automatically.

cre workflow simulate my-calculator-workflow --target staging-settings

The output shows the new user logs from your workflow, followed by the final Workflow Simulation Result.

Workflow compiled
2025-11-03T19:05:41Z [SIMULATION] Simulator Initialized

2025-11-03T19:05:41Z [SIMULATION] Running trigger trigger=[email protected]
2025-11-03T19:05:41Z [USER LOG] Hello, Calculator! Workflow triggered.
2025-11-03T19:05:41Z [USER LOG] Successfully fetched and aggregated math result: 60

Workflow Simulation Result:
 {
  "result": 60
}

2025-11-03T19:05:41Z [SIMULATION] Execution finished signal received
2025-11-03T19:05:41Z [SIMULATION] Skipping WorkflowEngineV2
  • [USER LOG]: You can now see both of your runtime.log() calls in the output. The second log shows the fetched value (e.g., 60), confirming that the API call and consensus worked correctly.
  • [SIMULATION]: These are system-level messages from the simulator showing its internal state.
  • Workflow Simulation Result: This is the final return value of your workflow. The result field now contains the aggregated value from the API as a number.

Your workflow can now fetch and process data from an external source.

Next Steps

Next, you'll learn how to interact with a smart contract to read data from the blockchain and combine it with this offchain result.

Get the latest Chainlink content straight to your inbox.