> For the complete documentation index, see [llms.txt](https://docs.catalyx.solutions/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.catalyx.solutions/catalyx-blockchain-manager/canton-network/version-1.11/validator-management/expose-ledger-api.md).

# Expose Ledger API

## Overview

This guide explains how to:

* expose the Ledger API for a participant or validator
* configure authentication and authorization
* configure Keycloak integration
* access the JSON Ledger API and gRPC Ledger API
* validate access tokens
* troubleshoot common Ledger API access issues

The Ledger API allows external applications to submit commands, query contracts, consume ledger events, and integrate with Canton applications.

{% hint style="info" %}
Enabling the Ledger API exposes the participant API externally but does **not** cause ledger downtime or interrupt validator operations.
{% endhint %}

## What is the Ledger API?

The Ledger API exposes services that allow applications to interact with the Canton ledger. It supports command submission, transaction streaming, active contract queries, and event subscriptions.

There are two primary API types:

| API Type        | Purpose                                    |
| --------------- | ------------------------------------------ |
| gRPC Ledger API | Native high-performance Canton integration |
| JSON Ledger API | REST/WebSocket-based integration           |

### Architecture

The Ledger API uses OpenID Connect (OIDC), JWT access tokens, Keycloak authentication, and Canton authorization checks. Authorization is performed for every request.

Required token claims:

* `aud` — audience
* `sub` — subject
* `scope` — must include `daml_ledger_api`

***

## Part 1 — Enable Ledger API Exposure

{% stepper %}
{% step %}

#### Open Validator Details

Navigate to **Validators**, select the target validator, and open **Edit configuration**.
{% endstep %}

{% step %}

#### Enable Ledger API Exposure

Locate the **Ledger API** or **API Exposure** settings and enable:

* JSON Ledger API exposure
* External API access

Save the configuration. After deployment, the participant exposes Ledger API endpoints externally.
{% endstep %}

{% step %}

#### Access the Ledger API URL

After enabling the API, the Ledger API URL becomes visible in the validator details page. Typical URL formats:

```
https://participant-${validator-name}.${domain}
```

```
https://json-api-${validator-name}.${domain}
```

Credentials are authenticated using Keycloak.
{% endstep %}
{% endstepper %}

***

## Part 2 — Configure Keycloak Authentication

Keycloak acts as the Identity Provider (IdP), OAuth Provider, and token issuer. Applications authenticate against Keycloak and receive JWT access tokens used by Canton.

### Create a Realm

{% stepper %}
{% step %}

#### Open Keycloak

Log in to the Keycloak Admin Console and click **Add Realm**.
{% endstep %}

{% step %}

#### Configure the Realm

Provide a **Realm Name** and **Display Name**, then click **Create**.

Use separate realms per environment, for example: `sandbox`, `production`.
{% endstep %}
{% endstepper %}

### Create an OpenID Client

{% stepper %}
{% step %}

#### Open Clients

Navigate to **Clients → Create**.
{% endstep %}

{% step %}

#### Configure the Client

Provide:

* **Client ID** — recommended: `canton-participants`
* **Protocol** — `openid-connect`
  {% endstep %}

{% step %}

#### Configure Access Type

Set **Access Type** to `confidential` and enable:

* Standard Flow
* Direct Access Grants
* Service Accounts

Save the configuration.
{% endstep %}

{% step %}

#### Configure Redirect URLs

Add the participant JSON API URL:

```
https://json-api-${participant}.${domain}/*
```

Save changes.
{% endstep %}
{% endstepper %}

### Create the `daml_ledger_api` Scope

{% stepper %}
{% step %}

#### Open Client Scopes

Navigate to **Client Scopes → Create**.
{% endstep %}

{% step %}

#### Configure the Scope

Create a scope named `daml_ledger_api`, enable **Include In Token Scope**, and save.
{% endstep %}

{% step %}

#### Assign the Scope to the Client

Navigate to **Client → Client Scopes** and add `daml_ledger_api` to **Assigned Default Client Scopes**.
{% endstep %}
{% endstepper %}

### Configure the Audience Mapper

The Ledger API validates the token audience against the participant identifier. Navigate to **Client Scopes → daml\_ledger\_api → Mappers** and create a mapper:

| Field                    | Value                         |
| ------------------------ | ----------------------------- |
| Name                     | `audience-participant-mapper` |
| Mapper Type              | Audience                      |
| Included Custom Audience | participant ID                |

Enable **Add to ID token** and **Add to access token**.

Example audience value:

```
sandbox::12205d66c5f04ee07...
```

***

## Part 3 — Configure Roles and Users

Navigate to **Client → Roles → Add Role** and create the required roles. Recommended roles:

* `Participant_Admin`
* `Party`
* `Broker`
* `Borrower`
* `Lender`

To create a user, navigate to **Users → Add User**, provide a username, email, and password, then navigate to **User → Role Mappings** and assign the appropriate client role (e.g. `Participant_Admin`).

***

## Part 4 — Configure Canton Authorization

Configure the participant to validate JWT tokens against Keycloak:

```
ledger-api {
    address = localhost
    port = 6865
    auth-services = [{
        type = jwt-rs-256-jwks
        url = "http://localhost:8080/auth/realms/company/protocol/openid-connect/certs"
    }]
}
```

***

## Part 5 — Generate an Access Token

Request a token from Keycloak:

```bash
curl -X POST \
  https://${keycloak}/auth/realms/${realm}/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=canton-participants" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "scope=daml_ledger_api" \
  -d "username=participant_admin" \
  -d "password=${PASSWORD}"
```

The response contains an access token, expiration, and token type.

### Validate the JWT Token

The JWT token must contain:

```json
{
  "aud": "participantId",
  "sub": "userId",
  "scope": "daml_ledger_api",
  "exp": 1300819380
}
```

* The `aud` audience must match the participant ID
* The `scope` must include `daml_ledger_api`

***

## Part 6 — Access the Ledger API

### JSON Ledger API

Used for REST integrations, WebSocket event streaming, frontend applications, and lightweight integrations. Include the JWT token in every request:

```
Authorization: Bearer <access_token>
```

Common endpoints:

| Endpoint            | Purpose                 |
| ------------------- | ----------------------- |
| `/v2/updates/flats` | Flat event stream       |
| `/v2/updates/trees` | Transaction tree stream |
| `/docs/openapi`     | OpenAPI documentation   |
| `/docs/asyncapi`    | AsyncAPI documentation  |

### gRPC Ledger API

Used for backend services, high-performance integrations, and native Canton clients. Main services: Command Submission Service, Command Completion Service, Command Service.

### Ledger Events

The Ledger API emits the following event types:

* Created Events
* Exercised Events
* Archived Events
* Transaction Tree Events

Applications consume events asynchronously.

***

## Troubleshooting

### Common Issues

| Issue                          | Cause                          | Resolution                           |
| ------------------------------ | ------------------------------ | ------------------------------------ |
| Unauthorized client            | Incorrect client configuration | Verify confidential client settings  |
| Invalid audience               | Wrong participant audience     | Update audience mapper               |
| Missing scope                  | `daml_ledger_api` not assigned | Add default client scope             |
| 401 Unauthorized               | Invalid token                  | Regenerate token                     |
| Ledger API inaccessible        | API not exposed                | Enable Ledger API exposure           |
| Create Party button greyed out | Parties not yet loaded         | Wait for synchronization to complete |

### Validation Checklist

| Check                              | Expected Result             |
| ---------------------------------- | --------------------------- |
| Ledger API enabled                 | External endpoint available |
| Keycloak client created            | OAuth authentication works  |
| `daml_ledger_api` scope configured | Tokens include scope        |
| Audience mapper configured         | Token audience valid        |
| User roles assigned                | API access granted          |
| JWT token valid                    | Requests authorized         |

***

## Best Practices

* Use confidential clients in production
* Separate environments by realm
* Use short-lived access tokens
* Restrict `Participant_Admin` role usage
* Always validate token scopes
* Use HTTPS-only endpoints
* Configure CORS for JSON API access

***

## Additional References

* [JSON Ledger API](https://docs.digitalasset.com/build/3.4/explanations/json-api/index.html)
* [Ledger API Services](https://docs.digitalasset.com/build/3.4/explanations/ledger-api-services.html)
* [Ledger API Proto Docs](https://docs.digitalasset.com/build/3.4/reference/lapi-proto-docs.html)
* [JSON API Reference](https://docs.digitalasset.com/build/3.4/reference/json-api/json-api.html)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.catalyx.solutions/catalyx-blockchain-manager/canton-network/version-1.11/validator-management/expose-ledger-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
