Getting Started

Authenticate, check dataset freshness and make your first requests.

🚧

Beta

Canton 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:

  1. Access to one or more tiers. Tiers (native, token and dtcc) are licensed independently. A request to a tier that is not licensed for your account returns 403. Blockdaemon confirms your licensed tiers when you are onboarded.
  2. 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.
  3. 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"
}
  • latestEventAt is 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.
  • status is healthy when the dataset is within its freshness target. It is degraded when queries still succeed but the data lags further behind the ledger than the target allows. Treat results as stale while it is degraded.
  • A 503 means 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"
}
StatusWhat to do
400Fix the request. details names the parameter.
401Get a fresh token.
403Check your licensed tiers and whether your token can read as the party you requested.
429Wait for the rate-limit window to reset, then retry.
500, 503Retry with backoff.

Next steps


Did this page help you?