Getting Started
Authenticate, check dataset freshness and make your first requests.
BetaCanton Data Services is in Beta. Endpoints and response fields can change as the product evolves.
This guide takes you from credentials to your first position query.
Prerequisites
You need three things:
- Access to one or more tiers. Tiers (
native,tokenanddtcc) are licensed independently. A request to a tier that is not licensed for your account returns403. Blockdaemon confirms your licensed tiers when you are onboarded. - A bearer token. Requests authenticate with an OAuth2 access token for the Ledger API audience, obtained through the client-credentials flow. The token determines which parties you can see. See Authentication.
- Party identifiers. Most queries filter on a party, written as
ALIAS::FINGERPRINT. Step 2 shows how to list the parties your token can read.
Set two environment variables for the examples on this page:
export BASE_URL="https://svc.blockdaemon.com/data/canton/mainnet"
export TOKEN="<your access token>"Step 1: Check dataset freshness
GET /native/health tells you how current the dataset is. It is the only endpoint that does not need a token, so you can use it for monitoring and liveness probes.
curl -s "$BASE_URL/native/health"import requests
print(requests.get(f"{BASE_URL}/native/health", timeout=30).json())const response = await fetch(`${BASE_URL}/native/health`);
console.log(await response.json());{
"data": {
"status": "healthy",
"chainId": "canton:mainnet",
"latestEventAt": "2026-08-07T22:09:58.120Z",
"ledgerOffset": "1491621"
},
"requestId": "4b90c6e7-1d38-4c52-8a06-9c2e7b5f3a17"
}latestEventAtis the commit time of the newest event available to query. The gap between it and the current time is the staleness of every result the API returns.statusishealthywhen the dataset is within its freshness target. It isdegradedwhen queries still succeed but the data lags further behind the ledger than the target allows. Treat results as stale while it isdegraded.- A
503means the dataset cannot be served at all. Retry with backoff.
Check this endpoint before you act on a result that needs to be current, such as an availability check.
Step 2: List the parties you can read
GET /native/parties returns the parties the participant knows about, and what your credential can do as each one.
curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/native/parties"result = get("/native/parties")
print(result)const result = await get("/native/parties");
console.log(JSON.stringify(result, null, 2));Each party includes its full partyId, plus hint and keyFingerprint, which are the two halves of the identifier. hasReadAsRight and hasActAsRight describe your credential, not the party. If a later query returns nothing for a party, check hasReadAsRight first: a party you cannot read as appears to have no data.
Step 3: Read a position
GET /token/positions returns one row per party and instrument, with the quantities already summed. This requires the token tier.
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/token/positions?partyId=<PARTY_ID>&hasAvailable=true"result = get(
"/token/positions",
partyId="<PARTY_ID>",
hasAvailable="true",
)
print(result)const result = await get("/token/positions", {
partyId: "<PARTY_ID>",
hasAvailable: "true",
});
console.log(JSON.stringify(result, null, 2));{
"data": [
{
"chainId": "canton:mainnet",
"partyId": "PARTY_A::1220a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"instrumentId": "912797RS8-912797RS8",
"instrumentAdminPartyId": "REGISTRY_REGISTRAR::1220e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1",
"total": { "amount": "200000020000", "amountFormatted": "20.0000020000" },
"locked": { "amount": "100000000000", "amountFormatted": "10.0000000000" },
"available": { "amount": "100000020000", "amountFormatted": "10.0000020000" },
"decimals": 10,
"holdingCount": 4,
"asOfOffset": "1491621"
}
],
"pagination": { "totalCount": 1 },
"requestId": "3e8a5c19-7b24-4d60-91f8-5a2c7e4b6d03"
}Quantities are strings. Use amount, the raw unscaled integer, for arithmetic, and amountFormatted for display. To interpret total, locked and available, see Positions & Availability Checks.
If you are licensed for the native tier only, start with GET /native/contracts or GET /native/updates instead. See Audit & Incremental Sync.
Step 4: Page through results
List endpoints return up to limit records per page, from 1 to 100, with a default of 20. When more records exist, the response includes pagination.nextPageToken. Pass it back as pageToken to get the next page. The token is opaque, so do not build or edit one.
This helper follows the cursor until the last page. The other guides reuse it.
def pages(path, **params):
"""Yield every record from a list endpoint, following pagination.nextPageToken."""
page_token = None
while True:
query = {**params, "limit": 100}
if page_token:
query["pageToken"] = page_token
body = get(path, **query)
yield from body["data"]
page_token = body.get("pagination", {}).get("nextPageToken")
if not page_token:
return
for position in pages("/token/positions", hasAvailable="true"):
print(position["partyId"], position["instrumentId"], position["available"]["amountFormatted"])export async function* pages<T = any>(path: string, params: Params = {}): AsyncGenerator<T> {
let pageToken: string | undefined;
do {
const body: { data: T[]; pagination?: { nextPageToken?: string } } = await get(path, {
...params,
limit: 100,
...(pageToken ? { pageToken } : {}),
});
yield* body.data;
pageToken = body.pagination?.nextPageToken;
} while (pageToken);
}
for await (const position of pages("/token/positions", { hasAvailable: true })) {
console.log(position.partyId, position.instrumentId, position.available.amountFormatted);
}Step 5: Handle errors
Every error uses one shape, and carries a requestId you can quote to support:
{
"error": {
"code": "invalid_parameter",
"message": "Parameter `limit` must be between 1 and 100.",
"details": { "parameter": "limit" }
},
"requestId": "9c1e7b04-5d38-4f62-a917-3b6c8d2e5f01"
}| Status | What to do |
|---|---|
400 | Fix the request. details names the parameter. |
401 | Get a fresh token. |
403 | Check your licensed tiers and whether your token can read as the party you requested. |
429 | Wait for the rate-limit window to reset, then retry. |
500, 503 | Retry with backoff. |
Next steps
Updated about 3 hours ago
