Architecture
Airnode is an HTTP server built on Bun.serve. It receives client requests, calls upstream APIs, signs the responses,
and returns the signed data. There is no chain scanning, no coordinator cycle, and no persistent state. The server
starts, loads the config, and serves requests.
Routes
| Method | Path | Description |
|---|---|---|
POST | /endpoints/{endpointId} | Call an endpoint with parameters in the request body |
GET | /requests/{requestId} | Poll an async request for its result |
GET | /health | Health check returning version and airnode address |
CORS preflight (OPTIONS) is handled automatically. Rate limiting uses a token bucket per client IP, configured via
server.rateLimit.
Request Processing Pipeline
Every POST /endpoints/{endpointId} request runs through a 13-step pipeline. Plugin hooks fire at defined points,
giving plugins the ability to observe, filter, or modify data at each stage.
- Resolve endpoint -- look up the endpoint by ID in the endpoint map. Returns 404 if not found.
- Plugin: onHttpRequest -- plugins can reject requests early (IP filtering, custom auth). A rejected request never reaches the API.
- Authenticate -- verify client credentials based on the configured method: free access (no check), API key via
X-Api-Keyheader, or x402 payment via on-chain transfer. - Validate parameters -- check that all required parameters (those without
fixedordefaultvalues) are present in the request body. - Check cache -- if the endpoint has cache config, return a cached response when the TTL has not expired.
- Plugin: onBeforeApiCall -- plugins can modify request parameters before the upstream call.
- Call API -- make the HTTP request to the upstream API via
src/api/call.ts. Method, path, headers, query parameters, and body are assembled from the endpoint config and client parameters. - Plugin: onAfterApiCall -- plugins can modify the API response before encoding.
- Encode -- if the endpoint has
encodingconfigured, extract the value atpathfrom the JSON response and ABI-encode it astypewith optionaltimesmultiplier. Endpoints without encoding return raw JSON. - Plugin: onBeforeSign -- plugins can modify the encoded data before signing.
- Sign -- EIP-191 personal sign over
keccak256(encodePacked(endpointId, timestamp, data)). The signature proves the data came from this airnode at this time for this endpoint. - Cache -- store the response if cache config is present. The
maxAgefield controls TTL. - Plugin: onResponseSent -- observation hook for logging, monitoring, or heartbeats. Cannot modify the response.
Error hooks (onError) fire when any stage fails, providing plugins with error context for alerting.
Request Flow
A client requests data, Airnode calls the API and responds.
Client Airnode Upstream API
│ │ │
│ POST /endpoints/{id} │ │
│ { parameters: {...} } │ │
│────────────────────────▶│ │
│ │ HTTP GET/POST │
│ │──────────────────────────▶│
│ │ │
│ │◀─────── JSON response ────│
│ │ │
│ ├── Encode (ABI or raw) │
│ ├── Sign (EIP-191) │
│ │ │
│◀────── signed response ─│ │
│ │ │
The response is self-contained: it includes the airnode address, endpoint ID, timestamp, encoded data, and signature. A client can verify the signature locally or submit it to an on-chain contract.
Signature Format
All responses are signed with the same scheme:
hash = keccak256(encodePacked(endpointId, timestamp, data))
signature = EIP-191 personal sign over hash
The three fields (endpointId, timestamp, data) are packed separately -- not nested in another hash. This enables
on-chain contracts to inspect each field independently for freshness checks and TLS proof verification.
For raw (unencoded) responses, data is the keccak256 hash of the JSON-serialized response. The full JSON is returned
in the rawData field alongside the signature over its hash.
Startup
When Airnode starts:
- Load and validate
config.yamlagainst the Zod schema. - Interpolate environment variables (
${VAR}references in the config). - Derive the airnode address from
AIRNODE_PRIVATE_KEYorAIRNODE_MNEMONIC. - Build the endpoint map: compute each endpoint ID and register it.
- Load plugins from their
sourcepaths. - Start the HTTP server on the configured port and host.
The server logs all registered endpoint IDs and the airnode address on startup.