Get started with Canton and the JSON Ledger API

This tutorial demonstrates how to interact with the Canton Ledger using the JSON Ledger API from the command line. It uses curl and optionally websocat to communicate with the Ledger.

Overview

This example shows how to:

  • enable the JSON Ledger API in the Canton configuration

  • create a contract using the JSON Ledger API and basic Bash tools (curl)

  • list active contracts using curl

  • basic error handling and troubleshooting

Prerequisites

Tools

Before running the project, ensure you have the following installed:

  • A Bash-compatible terminal (e.g., macOS Terminal, Git Bash, etc.)

  • Daml — install using:

curl -sSL https://get.daml.com/ | sh -s 

(check installed daml version entering in bash console daml version, is should be at least )

Daml Model

To demonstrate the JSON Ledger API, you need an example Daml model.

Run the following command in the console:

daml new json-tests

A folder named json-tests should be created. Check its contents and inspect the code in daml/Main.daml.

It should contain a Daml model with a template named Asset:

template Asset
  with
    issuer : Party
    owner  : Party
    name   : Text
  where
    ensure name /= ""
    signatory issuer
    observer owner
    choice Give : AssetId
      with
        newOwner : Party
      controller owner
      do create this with
        owner = newOwner

Before proceeding, compile the Daml model by running the following command inside the folder:

daml build

A file named .daml/dist/json-tests-0.0.1.dar should be created.

Starting Canton with JSON Ledger API

First, ensure you can run the Canton sandbox with the JSON Ledger API enabled.

Navigate to the folder where you unpacked the canton distribution. There should be a bin subfolder containing the canton executable.

cd <canton_installation>

Start the Canton sandbox, providing a path to the created dar file:

./bin/canton sandbox --json-api-port 7575 --dar <path_to_json_tests_project>/.daml/dist/json-tests-0.0.1.dar

Note

To simplify this tutorial, no security-related configuration is used. You will be able to send commands as any user.

Open a new Bash terminal, but keep the Canton sandbox running.

Verification - download OpenAPI

To verify that the JSON Ledger API is working, check if the documentation endpoint is available.

In a new Bash terminal, run:

curl localhost:7575/docs/openapi

You should receive a long YAML document starting with:

openapi: 3.0.3
info:
  title: JSON Ledger API HTTP endpoints
  version: 3.3.0
paths:
  /v2/commands/submit-and-wait:
    post:

This openapi.yaml document provides an overview of the available endpoints and can be pasted into a tool like https://editor-next.swagger.io.

It can also be used to generate client stubs in languages like Java or TypeScript.

Note

You can use the http://localhost:757/livez endpoint to check whether the server is running. It might be used as a health check in production environments.

Create a party

Run this command in the terminal:

curl -d '{"partyIdHint":"Alice", "identityProviderId": ""}' -H "Content-Type: application/json" -X POST localhost:7575/v2/parties

This should return a response similar to:

{"partyDetails":{"party":"Alice::122084768362d0ce21f1ffec870e55e365a292cdf8f54c5c38ad7775b9bdd462e141","isLocal":true,"localMetadata":{"resourceVersion":"0","annotations":{}},"identityProviderId":""}}

Take note of the party ID, which will be used in the following steps. In this example, it is:

Alice::122084768362d0ce21f1ffec870e55e365a292cdf8f54c5c38ad7775b9bdd462e141 — but this will differ in your case.

Create a contract

Create a file called create.json with the following content, replacing <partyId> with the actual party ID shown earlier:

{
  "commands": [
    {
      "CreateCommand": {
        "createArguments": {
          "issuer": "<partyId>",
          "owner": "<partyId>",
          "name": "Example Asset Name"
        },
        "templateId": "#json-tests:Main:Asset"
      }
    }
  ],
  "userId": "ledger-api-user",
  "commandId": "example-app-create-1234",
  "actAs": [
    "<partyId>"
  ],
  "readAs": [
    "<partyId>"
  ]
}

Now submit the request:

curl localhost:7575/v2/commands/submit-and-wait -H "Content-Type: application/json" -d@create.json

A successful response should look like:

{"updateId":"...","completionOffset":20}

This confirms that a contract was created on the ledger.

Query the ledger

To query the ledger for active contracts, first create a file named acs.json:

{
  "filter": {
    "filtersByParty": {},
    "filtersForAnyParty": {
      "cumulative": [
        {
          "identifierFilter": {
            "WildcardFilter": {
              "value": {
                "includeCreatedEventBlob": true
              }
            }
          }
        }
      ]
    }
  },
  "verbose": false,
  "activeAtOffset": <offset>,
  "eventFormat": null
}

Replace <offset> with the completionOffset you received earlier (e.g., 20).

Run the query:

curl localhost:7575/v2/state/active-contracts -H "Content-Type: application/json" -d@acs.json

You should receive a response containing contract information.

To improve readability, pipe the output to jq:

curl localhost:7575/v2/state/active-contracts -H "Content-Type: application/json" -d@acs.json | jq

Look for the createdEvent section, which contains contract details like:

"createdEvent": {
      "offset": 20,
      "nodeId": 0,
      "contractId": "00572c50513ced94f9cddaf1e6d2d3f050ae35d7fea0affe06f65f4238e84136edca1112202d01e45b5cfafb61e3942e4610547689dbebfebd3ea7d10d57944401fc17e81b",
      "templateId": "6fd1d46124d5ab0c958ce35e9bb370bb2835b2672a0d6fa039a3855c11b8801d:Main:Asset",
      "contractKey": null,
      "createArgument": {
        "issuer": "Alice::122084768362d0ce21f1ffec870e55e365a292cdf8f54c5c38ad7775b9bdd462e141",
        "owner": "Alice::122084768362d0ce21f1ffec870e55e365a292cdf8f54c5c38ad7775b9bdd462e141",
        "name": "Example Asset Name"
      },
  ...
}

Troubleshooting

If you encounter issues while calling the using curl - you should enable -v (verbose) mode to see the request and response details. For instance: .. code-block:

curl -v -d '{"partyIdHint":"Alice", "identityProviderId": ""}' -H "Content-Type: application/json" -X POST localhost:7575/v2/parties

Http response different than 200 (e.g., 400, 404, etc.) indicates an error. The response body will contain details about the error.

If it does not help, read logs available in the canton sandbox terminal or in the file <canton_installation>/logs/canton.log.

If nothing is returned when you query localhost:7575/v2/state/active-contracts ensure that the offset provided is correct and corresponds to the completionOffset from the localhost:7575/v2/commands/submit-and-wait command. You can also check current offset by running: .. code-block:

curl localhost:7575/v2/state/ledger-end

Next steps

Canton examples

For a more advanced scenario involving two parties, explore the examples provided with Canton installation: <canton_installation>/examples/json-ledger-api>

A TypeScript version is also available that demonstrates how to create a JSON Ledger API client for use in a web browser.

OpenAPI and AsyncAPI

Read more about the OpenAPI and AsyncAPI specifications for the Canton JSON Ledger API: JSON Ledger API references.

Authentication and security

Read how to configure and use jwt token to access JSON Ledger API Access Tokens.

Error codes

For more information about error codes returned by the JSON Ledger API, see HTTP Status Codes.