Skip to main content

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) or MultiHolderNFT (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 — ERC721 or ERC1155.
    • 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 FileType enum value (e.g., IMAGE_JPEG, IMAGE_PNG, VIDEO_MP4).
      • mediaType: The general media category as a MediaType enum 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.

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:

ArgumentTypeDescription
firstIntMaximum number of items to return (default: 10, max: 100).
afterCursorOpaque cursor from pageInfo.endCursor. Pass this to fetch the next page of results.
whereFilter inputFilter criteria for results (varies per query — e.g., TokenFilter, NFTFilter, ContractFilter).
orderByEnumField to sort results by (varies per query — e.g., TokenOrderBy, ContractOrderBy).
orderDirectionOrderDirectionSort direction: ASC (ascending) or DESC (descending).

All list responses include a consistent structure:

FieldTypeDescription
entries[Type!]!Array of results for the current page.
totalCountInt!Total number of items matching the query across all pages.
pageInfoPageInfo!Pagination metadata containing hasNextPage, endCursor, and count.
tip

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 like metadata, media, or name are 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 standards Array: Use the standards array on contract types to determine what token standards a contract implements, rather than checking individual boolean flags.
  • Paginate Sensibly: Choose a first value of 50–100 to balance response size vs. response time. Always check pageInfo.hasNextPage to know when you've reached the end, and use totalCount to show progress or total result counts in your UI.
  • Order Consistently: Use orderBy and orderDirection to ensure consistent ordering across pages, especially when paginating through large result sets.