Skip to content

Commit 2b0ca70

Browse files
committed
Add getting started guide, system overview, and troubleshooting
- consumers/getting-started.md: step-by-step from health check to signature verification with viem code examples - guides/system-overview.md: system diagram, component descriptions, pull/push flows, single vs multi-operator deployment patterns - troubleshooting.md: 14 common errors with causes and fixes - Updated sidebar with new Guides category and page ordering
1 parent 86ff48e commit 2b0ca70

4 files changed

Lines changed: 462 additions & 1 deletion

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
slug: /consumers/getting-started
3+
sidebar_position: 0
4+
---
5+
6+
# Getting Started
7+
8+
You have an airnode URL and want to get signed data. This walkthrough takes you from zero to a verified response.
9+
10+
## Step 1: Check if the airnode is running
11+
12+
```bash
13+
curl http://airnode.example.com/health
14+
```
15+
16+
```json
17+
{
18+
"status": "ok",
19+
"version": "2.0.0",
20+
"airnode": "0xd1e98F3Ac20DA5e4da874723517c914a31b0e857"
21+
}
22+
```
23+
24+
Save the `airnode` address. You need it to verify signatures.
25+
26+
## Step 2: Find your endpoint ID
27+
28+
The airnode operator provides the endpoint ID for each API route. It is a `bytes32` hash derived from the endpoint's OIS
29+
specification. The operator's documentation lists available endpoint IDs and their expected parameters.
30+
31+
## Step 3: Make a request
32+
33+
Call the endpoint with parameters in the JSON body.
34+
35+
```bash
36+
curl -X POST http://airnode.example.com/endpoints/0x1c3e0fa5ac82e5514e0e9abac98e0b8e6c58b7bea12ae0393e4e4abe64ab9620 \
37+
-H "Content-Type: application/json" \
38+
-H "X-Api-Key: your-key" \
39+
-d '{"parameters":{"ids":"ethereum","vs_currencies":"usd"}}'
40+
```
41+
42+
The response contains the data and a signature:
43+
44+
```json
45+
{
46+
"airnode": "0xd1e98F3Ac20DA5e4da874723517c914a31b0e857",
47+
"endpointId": "0x1c3e0fa5ac82e5514e0e9abac98e0b8e6c58b7bea12ae0393e4e4abe64ab9620",
48+
"timestamp": 1700000000,
49+
"data": "0x0000000000000000000000000000000000000000000000d8e29e69b3e1e80000",
50+
"signature": "0x3a4e...signed"
51+
}
52+
```
53+
54+
If the endpoint has no encoding configured, you get `rawData` instead of `data`:
55+
56+
```json
57+
{
58+
"airnode": "0xd1e98F3Ac20DA5e4da874723517c914a31b0e857",
59+
"endpointId": "0x1c3e0fa5ac82e5514e0e9abac98e0b8e6c58b7bea12ae0393e4e4abe64ab9620",
60+
"timestamp": 1700000000,
61+
"rawData": { "ethereum": { "usd": 3842.17 } },
62+
"signature": "0x3a4e...signed"
63+
}
64+
```
65+
66+
## Step 4: Verify the signature
67+
68+
Recover the signer address and compare it to the airnode address from `/health`.
69+
70+
```typescript
71+
import { recoverAddress, hashMessage, keccak256, encodePacked, type Hex } from 'viem';
72+
73+
const endpointId = '0x1c3e0fa5ac82e5514e0e9abac98e0b8e6c58b7bea12ae0393e4e4abe64ab9620' as Hex;
74+
const timestamp = 1700000000n;
75+
const data = '0x0000000000000000000000000000000000000000000000d8e29e69b3e1e80000' as Hex;
76+
const signature = '0x3a4e...signed' as Hex;
77+
const expectedAirnode = '0xd1e98F3Ac20DA5e4da874723517c914a31b0e857';
78+
79+
// Reconstruct the message hash
80+
const messageHash = keccak256(encodePacked(['bytes32', 'uint256', 'bytes'], [endpointId, timestamp, data]));
81+
82+
// Recover the signer
83+
const recovered = await recoverAddress({
84+
hash: hashMessage({ raw: messageHash }),
85+
signature,
86+
});
87+
88+
if (recovered.toLowerCase() !== expectedAirnode.toLowerCase()) {
89+
throw new Error('Signature verification failed');
90+
}
91+
```
92+
93+
For raw responses, hash the JSON before verifying:
94+
95+
```typescript
96+
import { toBytes, keccak256 as keccak } from 'viem';
97+
98+
const rawDataHash = keccak(toBytes(JSON.stringify(rawData)));
99+
// Use rawDataHash in place of `data` in the encodePacked call above
100+
```
101+
102+
## Step 5: Decode the data
103+
104+
Encoded responses contain ABI-encoded values. Decode them with viem.
105+
106+
```typescript
107+
import { decodeAbiParameters } from 'viem';
108+
109+
// For an int256 value (e.g., price with 18 decimals)
110+
const [value] = decodeAbiParameters([{ type: 'int256' }], data);
111+
112+
// Convert from 18 decimals to human-readable
113+
const price = Number(value) / 1e18;
114+
console.log(`ETH price: $${price}`); // ETH price: $3842.17
115+
```
116+
117+
Raw responses need no decoding. Access the JSON directly:
118+
119+
```typescript
120+
const ethPrice = rawData.ethereum.usd; // 3842.17
121+
```
122+
123+
## Step 6: Submit on-chain (optional)
124+
125+
Pass the signed data to an on-chain verifier contract. See [On-Chain Integration](/docs/consumers/on-chain) for contract
126+
examples using AirnodeVerifier (pull) and AirnodeDataFeed (push).
127+
128+
## Choosing encoding at request time
129+
130+
Some endpoints leave encoding unset, letting the client choose at request time. Pass reserved parameters in the request
131+
body:
132+
133+
```json
134+
{
135+
"parameters": {
136+
"ids": "ethereum",
137+
"vs_currencies": "usd",
138+
"_type": "int256",
139+
"_path": "ethereum.usd",
140+
"_times": "1000000000000000000"
141+
}
142+
}
143+
```
144+
145+
- `_type` -- the Solidity ABI type to encode as (`int256`, `uint256`, `bool`, `bytes32`, `address`, `string`, `bytes`)
146+
- `_path` -- dot-separated JSON path to extract from the upstream response
147+
- `_times` -- multiplier applied before encoding (use `1e18` for 18 decimal precision)
148+
149+
Both `_type` and `_path` are required together. `_times` is optional and defaults to no multiplication.
150+
151+
## What can go wrong
152+
153+
| Status | Error | What to do |
154+
| ------ | ------------------------------------------------ | ------------------------------------------------------------------------------- |
155+
| `400` | `Missing required parameter(s): X` | Add the missing parameters to your request body. |
156+
| `400` | `Both _type and _path are required for encoding` | You sent one reserved parameter without the other. Send both or neither. |
157+
| `401` | `Missing X-Api-Key header` | The endpoint requires authentication. Add `X-Api-Key: your-key` to the request. |
158+
| `401` | `Invalid API key` | The key value is wrong. Check with the airnode operator. |
159+
| `404` | `Endpoint not found` | The endpoint ID is incorrect. Verify the ID with the operator. |
160+
| `413` | `Request body too large` | The request body exceeds 64KB. Reduce the payload size. |
161+
| `415` | `Unsupported Media Type` | Set `Content-Type: application/json`. |
162+
| `429` | `Rate limit exceeded` | Wait and retry. The airnode has a request rate limit configured. |
163+
| `502` | `API call failed` | The upstream API is unreachable or returning errors. Try again later. |
164+
| `502` | `No value found at path: $.X` | The upstream response shape changed or the path is wrong. Contact the operator. |
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
slug: /guides/system-overview
3+
sidebar_position: 1
4+
---
5+
6+
# System Overview
7+
8+
Airnode connects APIs to blockchains. This page shows how the pieces fit together.
9+
10+
```
11+
┌──────────────┐
12+
│ Upstream │
13+
│ API │
14+
└──────┬───────┘
15+
16+
17+
┌─────────┐ POST /endpoints ┌──────────────┐ GET /beacons ┌──────────────┐
18+
│ Client │ ───────────────▶ │ Airnode │ ◀──────────── │ Relayer │
19+
│ (pull) │ ◀─── signed ─── │ │ ─── signed ──▶ │ │
20+
└─────────┘ response └──────────────┘ beacon └──────┬───────┘
21+
│ │
22+
│ (optional) │
23+
▼ ▼
24+
┌──────────────┐ ┌──────────────┐
25+
│ Cache Server │ │ On-chain │
26+
│ │ │ Contract │
27+
└──────────────┘ └──────────────┘
28+
```
29+
30+
The left side is the **pull path** (on-demand). The right side is the **push path** (continuous feeds).
31+
32+
## Components
33+
34+
**Airnode** -- the core HTTP server. Receives requests, calls upstream APIs, signs responses with the operator's private
35+
key. Stateless. Runs anywhere Bun runs.
36+
37+
**Cache Server** -- an optional intermediary that aggregates signed data from multiple airnodes. Stores the latest
38+
signed responses so clients and relayers do not need direct access to each airnode. Useful in multi-operator setups.
39+
40+
**Relayer** -- reads signed beacon data from an airnode or cache server and submits it on-chain at a configured cadence.
41+
Pays gas. Not part of the airnode itself -- it is a separate service.
42+
43+
**AirnodeVerifier** (contract) -- on-chain signature verification for the pull path. Recovers the signer, checks replay
44+
protection, and forwards data to a callback contract. See [Verifier](/docs/contracts/verifier).
45+
46+
**AirnodeDataFeed** (contract) -- on-chain storage for the push path. Stores `(int224, uint32)` beacon values. Anyone
47+
can submit signed data. Contracts read the latest value at any time. See [Data Feed](/docs/contracts/data-feed).
48+
49+
## Pull flow
50+
51+
1. Client sends `POST /endpoints/{endpointId}` with parameters to the airnode.
52+
2. Airnode authenticates the request (API key, free, or other configured method).
53+
3. Airnode calls the upstream API with the assembled HTTP request.
54+
4. Airnode encodes the response (ABI encoding or raw JSON) and signs it with EIP-191.
55+
5. Client receives the signed response containing `airnode`, `endpointId`, `timestamp`, `data`, and `signature`.
56+
6. Client verifies the signature off-chain by recovering the signer address.
57+
7. (Optional) Client submits the signed data to `AirnodeVerifier.verify_and_fulfill()` on-chain.
58+
8. AirnodeVerifier recovers the signer, enforces replay protection, and calls the consumer contract's callback.
59+
60+
## Push flow
61+
62+
1. Airnode's push loop fires at the configured `push.interval` for each push endpoint.
63+
2. Airnode calls the upstream API and ABI-encodes the response.
64+
3. Airnode signs the encoded data and stores it in the in-memory beacon store.
65+
4. A relayer polls `GET /beacons/{beaconId}` to fetch the latest signed beacon.
66+
5. The relayer submits the signed data to `AirnodeDataFeed.update_beacon()` on-chain.
67+
6. AirnodeDataFeed verifies the signature, checks timestamp freshness, and stores the value.
68+
7. Consumer contracts call `read_beacon(beaconId)` to get the latest `(int224 value, uint32 timestamp)`.
69+
70+
## Single-operator setup
71+
72+
The simplest deployment: one airnode serving data directly to clients.
73+
74+
```
75+
Client ──▶ Airnode ──▶ Upstream API
76+
```
77+
78+
This is enough when:
79+
80+
- One API provider operates one airnode.
81+
- Clients connect directly to the airnode (no aggregation needed).
82+
- You only use the pull path, or run your own relayer for push.
83+
84+
No cache server or external infrastructure required. The airnode is the entire backend.
85+
86+
## Multi-operator setup
87+
88+
Multiple independent airnodes serve the same API data. A cache server aggregates their signed responses. Relayers read
89+
from the cache server and submit on-chain with quorum verification.
90+
91+
```
92+
Airnode A ──┐
93+
Airnode B ──┼──▶ Cache Server ──▶ Relayer ──▶ On-chain Contract
94+
Airnode C ──┘
95+
```
96+
97+
Use this when:
98+
99+
- You need redundancy across independent operators.
100+
- On-chain consumers require data signed by multiple sources (beacon sets / quorum).
101+
- You want to decouple airnode operators from gas payment and on-chain submission.
102+
103+
Each airnode produces its own beacon ID for the same endpoint. The cache server collects all of them. A relayer can
104+
aggregate them into a beacon set on-chain using median aggregation.

0 commit comments

Comments
 (0)