Troubleshooting
Common errors, what causes them, and how to fix them.
Configuration errors
Environment variable X is referenced in config but not set
Cause: Your config.yaml uses ${VAR_NAME} interpolation, but the variable is not defined in the environment or
.env file.
Fix: Create a .env file in the same directory as config.yaml and define the variable. Bun loads .env
automatically -- no dotenv needed.
# .env
COINGECKO_URL=https://api.coingecko.com/api/v3
API_KEY_1=your-key-here
AIRNODE_PRIVATE_KEY environment variable is required
Cause: The server cannot start without a private key to derive the airnode address and sign responses.
Fix: Add AIRNODE_PRIVATE_KEY to your .env file. It must be a valid 32-byte hex string with the 0x prefix.
# .env
AIRNODE_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
A parameter cannot be both required and have a default value
Cause: An endpoint parameter in config.yaml has both required: true and a default value. These are mutually
exclusive -- a required parameter must come from the client, while a default provides a fallback when the client omits
it.
Fix: Remove either required or default from the parameter definition.
# Wrong
parameters:
- name: limit
required: true
default: '10'
# Correct: optional with a default
parameters:
- name: limit
default: '10'
# Correct: required, no default
parameters:
- name: limit
required: true
Client request errors
Endpoint not found (404)
Cause: The endpoint ID in the URL does not match any endpoint registered in the airnode's config.
Fix: Verify the endpoint ID is correct. The airnode logs all registered endpoint IDs on startup. Check the operator's documentation for the correct ID.
Missing X-Api-Key header (401)
Cause: The endpoint has auth.type: 'apiKey' configured, and the request does not include an X-Api-Key header.
Fix: Add the header to your request.
curl -X POST http://airnode.example.com/endpoints/0x... \
-H "Content-Type: application/json" \
-H "X-Api-Key: your-key" \
-d '{"parameters":{}}'
Invalid API key (401)
Cause: The X-Api-Key header value does not match any key in the endpoint's (or API's) auth.keys list.
Fix: Check the key value with the airnode operator. Keys are case-sensitive.
Missing required parameter(s): X (400)
Cause: The endpoint defines required parameters that are not present in the request body.
Fix: Include all required parameters in the parameters object of the request body. Check the endpoint
specification for the full parameter list.
Endpoint requires `_type` request parameter (400)
(Or `_path`, or `_times`.)
Cause: The operator marked one or more encoding fields with the wildcard '*'. Each wildcard field requires the
matching reserved parameter (_type / _path / _times) in the request body. This 400 means at least one is missing.
Fix: Supply every wildcarded field. The operator's documentation should list which fields are wildcarded — they
correspond to whichever ones appear as * in the canonical endpoint spec.
{
"parameters": {
"ids": "ethereum",
"vs_currencies": "usd",
"_type": "int256",
"_path": "$.ethereum.usd",
"_times": "1e18"
}
}
If the operator pinned a field, any reserved parameter you send for that field is silently ignored — the operator's
value wins. If the endpoint has no encoding block at all, you'll get raw JSON back regardless of reserved parameters.
Request body too large (413)
Cause: The request body exceeds the size limit. Airnode allows up to 64KB.
Fix: Reduce the request payload size. If you need to send large bodies, check whether the parameters can be simplified.
Too Many Requests (429)
Cause: You've exceeded server.rateLimit.max requests per IP within server.rateLimit.window. Each upstream API
call costs the operator (metered API quotas), so the airnode caps per-IP throughput.
Fix: Throttle your client. If you're behind a NAT or shared IP, ask the operator whether they trust an
X-Forwarded-For header from your environment — if so, they can enable rateLimit.trustForwardedFor and the limit
applies per real client.
Too many x402 verification attempts — slow down (401)
Cause: You're an x402 client and you're submitting payment proofs faster than server.rateLimit.x402.max per
window. This is a separate, stricter bucket from the global rate limit — each submitted proof triggers several
chain-RPC reads, so unauthenticated flooders are throttled hard.
Fix: Slow down proof submission. Only submit proofs for transactions you actually intend the airnode to verify;
don't speculatively spam unverified txHash values.
Payment required (402)
Cause: The endpoint has auth.type: 'x402' and you haven't supplied a valid payment proof.
Fix: The 402 response body includes airnode, endpointId, amount, token, network, recipient, and
expiresAt. Send the on-chain transfer, then retry with an X-Payment-Proof header containing
{ "txHash": "0x…", "expiresAt": <unix-seconds>, "signature": "0x…" }. The signature is over
keccak256(encodePacked(airnode, endpointId, uint64(expiresAt))) from the payer's EOA.
Server busy (503)
Cause: The airnode is already running settings.maxConcurrentApiCalls upstream requests and your request waited its
full timeout for a slot without getting one.
Fix: Operator-side: raise maxConcurrentApiCalls if the upstream can handle it, or front the airnode with a CDN
that caches frequent endpoints. Client-side: slow your request rate or add jitter.
Upstream API errors
API call failed (502)
Cause: The upstream API is unreachable, returned a non-2xx status code, or the response could not be parsed.
Fix: Verify the upstream API is accessible from the airnode's network. Check the apis[].url and endpoint path in
the config. Review airnode logs for the upstream status code and response body.
API returned no data to encode (502)
Cause: The upstream API returned an empty body (e.g., HTTP 204) on an endpoint that has encoding configured. Encoding requires a JSON response to extract a value from.
Fix: Check why the upstream API returns an empty response. The API route may require different parameters, or the endpoint path in the config may be wrong.
No value found at path: $.foo (502)
Cause: The JSONPath configured in the endpoint's encoding.path does not match the structure of the upstream API
response.
Fix: Inspect the actual upstream response and adjust the path in the endpoint's encoding config. Common causes:
the API changed its response format, a nested field was renamed, or the path uses the wrong separator.
FHE encryption errors
FHE encryption failed (502)
Cause: The endpoint has an encrypt block, but the relayer rejected the encryption attempt. Common subcauses appear
in the server log: a negative integer for an unsigned ciphertext (euint* types are unsigned), a value that overflows
the chosen ciphertext type, or the relayer being unreachable.
Fix: Check the airnode logs for the specific error. Common fixes: choose a larger encrypt.type (e.g. euint256
instead of euint64); pin a non-negative encoding (uint256 instead of int256); or verify settings.fhe.rpcUrl and
settings.fhe.apiKey are correct.
Endpoint requires FHE encryption but settings.fhe is not configured
Cause: An endpoint has encrypt: { ... } but settings.fhe is 'none'.
Fix: Either remove encrypt from the endpoint or set settings.fhe to a configured relayer block.
Config-validation catches this at startup, so seeing it at runtime usually means the config was edited without
restarting.
Plugin errors
Plugin "X" budget exhausted (request dropped, 403)
Cause: A plugin defining a mutation hook (onHttpRequest, onBeforeApiCall, onAfterApiCall, onBeforeSign) has
spent its full timeout budget on previous hook invocations within the same request. Mutation hooks are fail-closed —
once the budget is gone, the request is dropped rather than being processed without the plugin's intervention.
Fix: Operator-side: raise the plugin's timeout in settings.plugins[].timeout. Plugin-author side: pass each
hook's signal to your fetch calls so cancellation actually propagates, and avoid unnecessary work in earlier hooks.
Plugin runs only on the first request in a cache window
Cause: Not an error — by design. Cached responses bypass the upstream API call, which also bypasses the
onBeforeApiCall, onAfterApiCall, and onBeforeSign hooks. Only onHttpRequest, onResponseSent, and onError
fire on every request.
Fix: If you need per-request signal, use onResponseSent or onHttpRequest. See
Plugins → Caching interaction.
Async endpoint errors
Request not found (404) on GET /requests/{requestId}
Cause: The request ID is wrong, or it's older than the async store's retention window (10 minutes for in-flight requests, 1 minute for completed/failed results). Finished results are evicted promptly so an unrelated request can take its slot.
Fix: Poll within the retention window. If you waited longer, the result is gone — re-submit the request.
Service Unavailable (503) from a mode: async endpoint
Cause: The async store is at its 100-entry cap and no slot can be safely evicted (every entry is still in-flight within its TTL or holds an unread result within its retention window).
Fix: This indicates sustained submission rate exceeding the airnode's async capacity. Wait, retry, or have the
operator review whether mode: async is appropriate for that workload.
CORS errors
Browser rejects response: CORS policy: No 'Access-Control-Allow-Origin' header
Cause: The airnode's server.cors is configured with an origins allow-list that doesn't include your origin.
Non-matching origins receive Access-Control-Allow-Origin: null, which browsers refuse.
Fix: Operator-side: add your origin to server.cors.origins, or remove the cors block entirely (defaults to
allowing every origin). Verify with curl -i -H 'Origin: https://your-app.example' … — the airnode echoes the matched
origin back in the response header.
General debugging
Check the logs. Airnode logs every request with its endpoint ID, response status, and processing time. Set
LOG_LEVEL=debug for detailed pipeline output including upstream request/response details.
Verify the config. Run airnode config validate -c config.yaml to check your config against the schema before
starting the server.
Test the upstream API directly. Use curl to call the upstream API with the same parameters the airnode would use. This isolates whether the issue is in the airnode or the upstream.
# Example: test the upstream directly
curl "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"