Working With API Responses
Structure of the Response
This is an example response you might get when executing an nft query.
{
"data": {
"nft": {
"tokenId": "42141102",
"name": "Singular Focus",
"standard": "ERC721",
"contract": {
"address": "0xC9041f80DCe73721A5f6a779672Ec57EF255d27c",
"name": "Art Blocks",
"symbol": "BLOCKS",
"standards": ["ERC721"]
},
"metadata": {
"name": "Singular Focus",
"description": "The Partition is formed...",
"image": "ipfs://QmExample...",
"attributes": [
{
"traitType": "Artist",
"value": "Example Artist"
}
]
},
"media": [
{
"fileType": "IMAGE_PNG",
"mediaType": "IMAGE",
"thumbnails": [
{
"url": "https://media.onesource.io/thumbnails/medium/1/42141102.png",
"preset": "MEDIUM",
"format": "png",
"fileSize": 45230
}
],
"updatedAt": "2025-01-10T08:30:00Z"
}
]
}
}
}
Response Structure
Here's the general response structure for the above nft query response.
{
"data": {
"nft": {
"tokenId": "String (TokenId)",
"name": "String",
"standard": "Standard (ERC721 | ERC1155)",
"contract": {
"address": "String (AddressString)",
"name": "String",
"symbol": "String",
"standards": ["Standard"]
},
"metadata": {
"name": "String",
"description": "String",
"image": "String",
"attributes": [
{
"traitType": "String",
"value": "String"
}
]
},
"media": [
{
"fileType": "FileType",
"mediaType": "MediaType",
"thumbnails": [
{
"url": "String",
"preset": "ThumbnailPreset",
"format": "String",
"fileSize": "Int"
}
],
"updatedAt": "Timestamp"
}
]
}
}
}
Response Object Definitions
- data: The root object containing the response data.
- nft: The object representing the NFT details. May resolve to a
SingleHolderNFT(ERC-721) orMultiHolderNFT(ERC-1155).- tokenId: The unique token identifier within its contract (string, to support uint256 values).
- name: The human-readable name of the NFT from its metadata.
- standard: The token standard —
ERC721orERC1155. - contract: The NFT collection contract.
- address: The contract address as a checksummed hex string.
- name: The collection name.
- symbol: The collection symbol.
- standards: Array of token standards implemented by this contract.
- metadata: Parsed metadata for the NFT.
- name: The name from the token metadata.
- description: The description from the token metadata.
- image: The image URL from the token metadata.
- attributes: Array of trait objects.
- traitType: The trait category (e.g., "Background", "Eyes").
- value: The trait value (e.g., "Blue", "Laser Eyes").
- media: Array of media files associated with the NFT.
- fileType: The specific file format as a
FileTypeenum value (e.g.,IMAGE_JPEG,IMAGE_PNG,VIDEO_MP4). - mediaType: The general media category as a
MediaTypeenum value (IMAGE,VIDEO,AUDIO,ANIMATION,DOCUMENT). - thumbnails: Array of pre-generated thumbnail images.
- url: The thumbnail URL.
- preset: The thumbnail size preset (
MICRO,SMALL,MEDIUM,LARGE,XLARGE,ORIGINAL). - format: The image format of the thumbnail (e.g.,
webp,png,jpeg). - fileSize: The file size of the thumbnail in bytes, or null if not available.
- updatedAt: Timestamp when this media was last processed or refreshed.
- fileType: The specific file format as a
Accessing the Data
The response structure is consistent and its fields can be accessed in a predictable manner.
Below are some examples of how the response fields may be accessed in a project.
JavaScript
if (response.data && response.data.nft) {
const nft = response.data.nft;
const tokenId = nft.tokenId;
const tokenName = nft.name;
const standard = nft.standard;
const contractAddress = nft.contract.address;
const description = nft.metadata?.description;
const imageUrl = nft.metadata?.image;
const thumbnailUrl = nft.media?.[0]?.thumbnails?.[0]?.url;
console.log("Token ID: " + tokenId);
console.log("Name: " + tokenName);
console.log("Standard: " + standard);
console.log("Contract: " + contractAddress);
console.log("Description: " + description);
console.log("Image URL: " + imageUrl);
console.log("Thumbnail URL: " + thumbnailUrl);
} else {
console.error("Invalid response structure");
}
Python
if "data" in response and "nft" in response["data"]:
nft = response["data"]["nft"]
token_id = nft["tokenId"]
token_name = nft.get("name")
standard = nft.get("standard")
contract_address = nft["contract"]["address"]
description = nft.get("metadata", {}).get("description")
image_url = nft.get("metadata", {}).get("image")
media = nft.get("media", [])
thumbnail_url = None
if media and media[0].get("thumbnails"):
thumbnail_url = media[0]["thumbnails"][0].get("url")
print("Token ID:", token_id)
print("Name:", token_name)
print("Standard:", standard)
print("Contract:", contract_address)
print("Description:", description)
print("Image URL:", image_url)
print("Thumbnail URL:", thumbnail_url)
else:
print("Invalid response structure")
Handling Errors
Since the response is dynamic, developers should always check for errors and validate the structure of the response before accessing data.
JavaScript
if (response.errors) {
console.error("GraphQL Error: " + response.errors[0].message);
} else if (response.data && response.data.nft) {
const standard = response.data.nft.standard;
const standards = response.data.nft.contract.standards;
console.log("Token standard: " + standard);
console.log("Contract implements: " + standards.join(", "));
} else {
console.error("Invalid response structure");
}
Python
if "errors" in response:
print("GraphQL Error: " + response["errors"][0]["message"])
elif "data" in response and "nft" in response["data"]:
standard = response["data"]["nft"].get("standard")
standards = response["data"]["nft"]["contract"].get("standards", [])
print("Token standard:", standard)
print("Contract implements:", ", ".join(standards))
else:
print("Invalid response structure")
Common Use Cases
Displaying NFT Information
JavaScript
function displayNFTInfo(response) {
if (response.data?.nft) {
const nft = response.data.nft;
document.getElementById("token-name").innerText = nft.name || "Unnamed";
document.getElementById("token-description").innerText =
nft.metadata?.description || "";
// Use thumbnail for optimized loading
const thumbnail = nft.media?.[0]?.thumbnails?.[0];
if (thumbnail) {
const img = document.getElementById("token-image");
img.src = thumbnail.url;
}
// Display token standard
document.getElementById("token-type").innerText =
nft.standard === "ERC721" ? "ERC-721 (Unique)" :
nft.standard === "ERC1155" ? "ERC-1155 (Semi-fungible)" :
"Unknown";
} else {
console.error("Invalid response structure");
}
}
Filtering Contracts by Standard
Python
def filter_contracts_by_standard(response):
if "data" in response and "contracts" in response["data"]:
contracts = response["data"]["contracts"]["entries"]
erc20 = [c for c in contracts if "ERC20" in c.get("standards", [])]
erc721 = [c for c in contracts if "ERC721" in c.get("standards", [])]
erc1155 = [c for c in contracts if "ERC1155" in c.get("standards", [])]
print(f"ERC-20 Tokens: {len(erc20)}")
print(f"ERC-721 Collections: {len(erc721)}")
print(f"ERC-1155 Collections: {len(erc1155)}")
else:
print("Invalid response structure")
Ordering
OneSource databases are built with PostgreSQL which does not have default ordering.
The only way to ensure consistent ordering of results within responses is to use the sorting arguments orderBy and orderDirection.
Example
query GetWalletTokenBalances {
address(address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") {
tokenBalances(
first: 20
orderBy: VALUE
orderDirection: DESC
) {
totalCount
pageInfo {
hasNextPage
endCursor
}
entries {
token {
name
symbol
address
}
value {
formatted
decimals
}
}
}
}
}
Pagination
When querying large datasets, it is best to paginate through results to avoid requesting the entire dataset in a single response.
Refer to the GraphQL documentation on pagination for a complete overview of GraphQL pagination.
OneSource uses cursor-based pagination. All list queries accept these arguments:
| Argument | Type | Description |
|---|---|---|
first | Int | Maximum number of items to return (default: 10, max: 100). |
after | Cursor | Opaque cursor from pageInfo.endCursor. Pass this to fetch the next page of results. |
where | Filter input | Filter criteria for results (varies per query — e.g., TokenFilter, NFTFilter, ContractFilter). |
orderBy | Enum | Field to sort results by (varies per query — e.g., TokenOrderBy, ContractOrderBy). |
orderDirection | OrderDirection | Sort direction: ASC (ascending) or DESC (descending). |
All list responses include a consistent structure:
| Field | Type | Description |
|---|---|---|
entries | [Type!]! | Array of results for the current page. |
totalCount | Int! | Total number of items matching the query across all pages. |
pageInfo | PageInfo! | Pagination metadata containing hasNextPage, endCursor, and count. |
Use orderBy and orderDirection in conjunction with pagination when querying large datasets to ensure consistent ordering across pages.
Example
query ListTokens($after: Cursor) {
tokens(first: 50, orderBy: NAME, orderDirection: ASC, after: $after) {
totalCount
pageInfo {
hasNextPage
endCursor
count
}
entries {
address
name
symbol
holdersCount
}
}
}
JavaScript
const PAGE_SIZE = 50;
let after = null;
let allTokens = [];
let totalCount = 0;
while (true) {
const query = `
query ListTokens($after: Cursor) {
tokens(first: ${PAGE_SIZE}, orderBy: NAME, orderDirection: ASC, after: $after) {
totalCount
pageInfo {
hasNextPage
endCursor
}
entries {
address
name
symbol
holdersCount
}
}
}
`;
const response = await fetch("https://api.onesource.io/federation/ethereum/graphql", {
method: "POST",
headers: {
"x-bp-token": ONESOURCE_APIKEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
query,
variables: { after }
}),
}).then((r) => r.json());
const result = response.data?.tokens;
if (!result || result.entries.length === 0) break;
allTokens.push(...result.entries);
totalCount = result.totalCount;
if (!result.pageInfo.hasNextPage) break;
after = result.pageInfo.endCursor;
}
console.log(`Fetched ${allTokens.length} of ${totalCount} total tokens`);
Python
import requests
PAGE_SIZE = 50
after = None
all_tokens = []
total_count = 0
while True:
query = """
query ListTokens($after: Cursor) {
tokens(first: %s, orderBy: NAME, orderDirection: ASC, after: $after) {
totalCount
pageInfo {
hasNextPage
endCursor
}
entries {
address
name
symbol
holdersCount
}
}
}
""" % PAGE_SIZE
response = requests.post(
"https://api.onesource.io/federation/ethereum/graphql",
headers={
"x-bp-token": ONESOURCE_APIKEY,
"Content-Type": "application/json"
},
json={"query": query, "variables": {"after": after}}
).json()
result = response.get("data", {}).get("tokens", {})
entries = result.get("entries", [])
page_info = result.get("pageInfo", {})
total_count = result.get("totalCount", 0)
all_tokens.extend(entries)
if not page_info.get("hasNextPage"):
break
after = page_info.get("endCursor")
print(f"Fetched {len(all_tokens)} of {total_count} total tokens")
Best Practices
- Validate the Response Structure: Always check if the expected fields exist before accessing them.
- Handle Missing Fields Gracefully: Use optional chaining (JavaScript) or
.get()(Python) to avoid runtime errors when fields likemetadata,media, ornameare null. - Cache Responses: Cache frequently accessed data to reduce API calls and improve performance.
- Use TypeScript or Type Annotations: If possible, use TypeScript (JavaScript) or type annotations (Python) to ensure type safety when working with the response.
- Check
standardsArray: Use thestandardsarray on contract types to determine what token standards a contract implements, rather than checking individual boolean flags. - Paginate Sensibly: Choose a
firstvalue of 50–100 to balance response size vs. response time. Always checkpageInfo.hasNextPageto know when you've reached the end, and usetotalCountto show progress or total result counts in your UI. - Order Consistently: Use
orderByandorderDirectionto ensure consistent ordering across pages, especially when paginating through large result sets.