From somnia-agents-skills
Deep-dive reference for the JSON API Request agent on Somnia — fetch values from any public JSON API and bring them on-chain. Covers the 6 fetch functions (string / uint / int / bool / arrays), selector path syntax, decimal scaling, and Solidity / JS examples. Use when building price feeds, weather oracles, sports-score markets, or any contract that needs data from a REST API.
How this skill is triggered — by the user, by Claude, or both
Slash command
/somnia-agents-skills:somnia-agents-json-fetchThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The JSON API Request agent (`json-fetch`) is the cheapest and most general-purpose Somnia agent. It performs a single HTTP `GET`, parses the JSON response, walks a dot-notation selector to a value, and returns that value ABI-encoded as the requested Solidity type. Foundational building block for **any on-chain oracle backed by a public REST API** — price feeds, weather data, sports scores, gove...
The JSON API Request agent (json-fetch) is the cheapest and most general-purpose Somnia agent. It performs a single HTTP GET, parses the JSON response, walks a dot-notation selector to a value, and returns that value ABI-encoded as the requested Solidity type. Foundational building block for any on-chain oracle backed by a public REST API — price feeds, weather data, sports scores, governance snapshots.
Read the master
somnia-agentsskill first for the request lifecycle, gas model, and callback pattern. This document only covers the agent-specific ABI and quirks.
| Field | Value |
|---|---|
agentId | 13174292974160097713 |
| Per-agent price | 0.03 (whole tokens — SOMI on Mainnet, STT on Testnet) |
| Default consensus | Majority (deterministic — same URL + selector → same value across validators) |
| Source of truth | references/agents.json |
Same agentId on Mainnet and Testnet.
| Function | Inputs | Output | Use case |
|---|---|---|---|
fetchString(url, selector) | string, string | string | Token symbols, names, status text |
fetchUint(url, selector, decimals) | string, string, uint8 | uint256 | Prices, supply, positive counts |
fetchInt(url, selector, decimals) | string, string, uint8 | int256 | Temperatures, signed deltas |
fetchBool(url, selector) | string, string | bool | Market open/closed, whitelist flags |
fetchStringArray(url, selector) | string, string | string[] | Lists of names / IDs |
fetchUintArray(url, selector, decimals) | string, string, uint8 | uint256[] | Numeric arrays (price lists, vote tallies) |
urlAny https:// (or http://) endpoint that returns JSON. Must be publicly reachable — the agent runs in an isolated sandbox without auth or VPN access. Don't include API keys in the URL unless you're comfortable with them being visible in RequestCreated.payload events on-chain.
selector — dot-notation pathThe selector walks the parsed JSON to a single value (or, for the array variants, to a JSON array).
| Syntax | Means |
|---|---|
bitcoin.usd | body["bitcoin"]["usd"] |
data.price | body["data"]["price"] |
items[0].name | body["items"][0]["name"] |
result.symbols[3] | body["result"]["symbols"][3] |
| `` (empty string) | the entire response body |
The agent navigates the selector and serialises the final JSON value to the requested Solidity type. If the selector points to a non-existent path, an incompatible type, or a missing array index, the agent reports a Failed response and the request finalises with status Failed.
decimals (uint / int / uintArray only)JSON numbers are typically floats (e.g. 42000.50), but Solidity has no native floats. decimals is how many places to multiply by before the value is cast to uint256 / int256:
| Raw JSON value | decimals | Returned uint256 |
|---|---|---|
42000.50 | 8 | 4200050000000 |
0.00001234 | 8 | 1234 |
1 | 18 | 1000000000000000000 |
12345 | 0 | 12345 |
Use 8 for crypto prices (matches Chainlink's standard), 18 for token amounts, 0 for already-integer values like block heights or counts.
fetchInt follows the same rule and signs the result; negative JSON numbers map to negative int256.
A single Bitcoin price oracle (deterministic — Majority consensus is fine):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {
IAgentRequester,
IAgentRequesterHandler,
Response,
Request,
ResponseStatus
} from "./IAgentRequester.sol";
interface IJsonApiAgent {
function fetchUint(string calldata url, string calldata selector, uint8 decimals)
external returns (uint256);
}
contract BtcPriceOracle is IAgentRequesterHandler {
IAgentRequester public immutable platform;
uint256 public constant AGENT_ID = 13174292974160097713;
uint256 public constant SUBCOMMITTEE_SIZE = 3;
uint256 public constant PRICE_PER_AGENT = 0.03 ether;
uint256 public latestPrice; // BTC/USD × 1e8
uint256 public lastUpdated;
mapping(uint256 => bool) public pendingRequests;
constructor(address platform_) { platform = IAgentRequester(platform_); }
function refresh() external payable returns (uint256 requestId) {
bytes memory payload = abi.encodeWithSelector(
IJsonApiAgent.fetchUint.selector,
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
"bitcoin.usd",
uint8(8)
);
uint256 deposit = platform.getRequestDeposit() + PRICE_PER_AGENT * SUBCOMMITTEE_SIZE;
require(msg.value >= deposit, "Underfunded");
requestId = platform.createRequest{value: deposit}(
AGENT_ID, address(this), this.handleResponse.selector, payload
);
pendingRequests[requestId] = true;
}
function handleResponse(
uint256 requestId,
Response[] memory responses,
ResponseStatus status,
Request memory /* details */
) external override {
require(msg.sender == address(platform), "Only platform");
require(pendingRequests[requestId], "Unknown request");
delete pendingRequests[requestId];
if (status == ResponseStatus.Success && responses.length > 0) {
latestPrice = abi.decode(responses[0].result, (uint256));
lastUpdated = block.timestamp;
}
}
receive() external payable {}
}
Independent CoinGecko / Coinbase / Binance fetches will return slightly different prices. Use Threshold consensus and aggregate in-contract:
import { ConsensusType } from "./IAgentRequester.sol";
uint256 constant SUBCOMMITTEE_SIZE = 5;
uint256 constant THRESHOLD = 3; // 3 of 5 — tolerates 2 failures
function refreshMedian() external payable returns (uint256 requestId) {
bytes memory payload = abi.encodeWithSelector(
IJsonApiAgent.fetchUint.selector,
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
"bitcoin.usd",
uint8(8)
);
uint256 deposit = platform.getAdvancedRequestDeposit(SUBCOMMITTEE_SIZE)
+ PRICE_PER_AGENT * SUBCOMMITTEE_SIZE;
require(msg.value >= deposit, "Underfunded");
requestId = platform.createAdvancedRequest{value: deposit}(
AGENT_ID, address(this), this.handleResponse.selector, payload,
SUBCOMMITTEE_SIZE, THRESHOLD, ConsensusType.Threshold, 1 minutes
);
pendingRequests[requestId] = true;
}
// In handleResponse: collect successful responses, take the median, store.
Note: with Threshold consensus each validator independently fetches the price — they may hit the API near-simultaneously and get nearly-identical results, so the variance is small. For real diversity, run different validators against different upstream APIs (this requires either running multiple requests or a multi-source agent — outside the scope of json-fetch today).
import { encodeFunctionData, parseAbi } from 'viem';
const abi = parseAbi([
'function fetchString(string url, string selector) returns (string)',
'function fetchUint(string url, string selector, uint8 decimals) returns (uint256)',
'function fetchInt(string url, string selector, uint8 decimals) returns (int256)',
'function fetchBool(string url, string selector) returns (bool)',
'function fetchStringArray(string url, string selector) returns (string[])',
'function fetchUintArray(string url, string selector, uint8 decimals) returns (uint256[])',
]);
const payload = encodeFunctionData({
abi,
functionName: 'fetchUint',
args: [
'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd',
'bitcoin.usd',
8,
],
});
Pass payload as the 4th arg to createRequest. The full ABI definition is in references/agents.json under agents["json-fetch"].abi.
fetchUint(
"https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd",
"ethereum.usd",
8
)
fetchString(
"https://api.example.com/tokens/0xA0b8...",
"data.symbol"
)
fetchUintArray(
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,solana&vs_currencies=usd",
"", // entire body — use only when you control the response shape
8
)
(For arbitrary endpoints, prefer separate fetchUint calls with explicit selectors — empty-selector array fetches are brittle.)
fetchBool(
"https://api.example.com/market/status",
"isOpen"
)
fetchUint(
"https://api.sportsdata.io/v3/nba/scores/json/Game/12345?key=PUBLIC_KEY",
"HomeTeamScore",
0 // already an integer
)
RequestCreated.payload is emitted in plaintext on-chain. API keys, basic-auth credentials, and signed URLs are all visible to anyone watching the chain. Use only public endpoints, or proxy through a public endpoint that injects the key server-side.
JSON numbers above 2^53 - 1 lose precision in standard JSON parsers. For very large numeric values (gas-limit-style numbers), prefer endpoints that return them as JSON strings and use fetchString + decode in-contract.
fetchUintArray requires the selector to land on a JSON array of numbers. Pointing it at an object yields a Failed response. Use fetchString if you're not sure about the response shape and inspect via the receipt.
Public APIs (CoinGecko, etc.) rate-limit aggressively. Multiple validators each hit the upstream — if the API rejects some validators, the request can Failed with too few successful responses to reach Majority. For production, prefer endpoints with generous quotas or a dedicated paid tier.
A common bug: API returns 42000.5 and the contract expects 1e18 precision. With decimals=8 you get 4200050000000, but if you decode it as if it were 1e18-scaled you'll get a price of 0.0000042. Always document the decimal contract on the storage variable (uint256 public price; // BTC/USD × 1e8).
In order of likelihood:
perAgentBudget = 0. Send getRequestDeposit() + 0.03 × subSize.Failed. Check the receipt for http_response step.Failed. Inspect a receipt to see the actual JSON.responses.length > 0 guards.somnia-agents — request lifecycle, deposit math, callback patternsomnia-agents-invoke — interactive CLI to fire one-off fetchUint calls without writing a contractsomnia-agents-llm-parse-website — when the data is on a webpage, not a JSON APInpx claudepluginhub emrestay/somnia-agents-skills --plugin somnia-agents-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.