API Documentation

Authentication

You need to be authenticated for most API requests. You can generate a party specific API key in the form of a JWT token in your Project Settings dashboard. Choose the DAML Party that will authorize the requests and then copy the token to your clipboard in the format of your choice. Copy token to clipboard

Add the JWT token to all requests as an HTTP Header parameter, in order to authenticate them to that party.

Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRhYmwtMjAxOS0wOC0wNy0xNz...

Most API requests require authentication. Requests that require authentication will fail unless you include a JWT Token.

For streaming endpoints, in order to accommodate the requirement for an authentication token within the websocket protocol, the token needs to be passed by the "subprotocol" header. It is usually specified as a list, but can occasionally be specified in comma-separated form. Check the documentation for your WebSocket library of choice for details.

You must pass two subprotocols to establish proper handshake:

  • daml.ws.auth
  • jwt.token.[your-jwt-token-here]
Sec-Websocket-Protocol: jwt.token.eyJhbGciOiJSUzI1NiIsImtpZCI6ImRhYmwtMjAxOS0wOC0wNy0xNz..., daml.ws.auth

Endpoints

DABL supports a mirror of DAML SDK's JSON API endpoints:

Find full API Specs here.

Authenticating End Users

There are three kinds of end users that can interact with your deployed application. The following sections discuss how to authenticate people in each of these categories.

Reading Data from the Ledger

GET /data/:ledgerId/v1/query

Description

The following example demonstrates fetch all contracts for given party token.

HTTP GET request

GET /data/:ledgerId/v1/query HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN

HTTP success Response

{
     "result": [
         {
             "agreementText": "",
             "contractId": "#48:0",
             "observers": [],
             "payload": {
                 "issuer": "f8htpbsywu4nef6x",
                 "owner": "f8htpbsywu4nef6x",
                 "name": "squidly"
             },
             "signatories": [
                 "f8htpbsywu4nef6x"
             ],
             "templateId": "b3488a9041bb7994eb9ec629cb700e78c1c619e9be7d711ff22b0cadfcce3fbe:Main:Asset"
         }
     ],
     "status": 200
 }

POST /data/:ledgerId/v1/query

Description

POST request with search query in JSON body for matching contracts based on user-defined criteria. Please see detailed reference for contract search language.

The following will fetch all contracts with templateId Iou.Iou

HTTP POST request

POST /data/:ledgerId/v1/query HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application/json
 {
     "templateIds": ["Iou:Iou"],
     "query": {"amount": 100.0}
 }

HTTP success Response

{
     "result": [
         {
             "agreementText": "",
             "contractId": "#470:0",
             "observers": [],
             "payload": {
                 "observers": [],
                 "issuer": "rztds0vfs6hgd1np",
                 "amount": "100.0",
                 "currency": "USD",
                 "owner": "rztds0vfs6hgd1np"
             },
             "signatories": [
                 "rztds0vfs6hgd1np"
             ],
             "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:Iou"
         }
     ],
     "status": 200
 }

Empty Response

{
     "status": 200,
     "result": []
 }

HTTP Error Response

{
     "status": 400,
     "errors": [
         "JsonReaderError. Cannot read JSON: <{\"%templates\":[{\"moduleName\":\"Iou\"}]}>. Cause: Object is missing required member 'entityName'"
     ]
 }

POST /data/:ledgerId/v1/fetch

Description

POST request to lookup contract by contractID or contractKey.

The following will lookup contracts with contractId #1:0.

HTTP POST request

POST /data/:ledgerId/v1/fetch HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application/json
 {
   "contractId": "#1:0"
 }

Contract Found response

{
     "result": {
         "agreementText": "",
         "contractId": "#1:0",
         "observers": [],
         "payload": {
             "observers": [],
             "issuer": "rztds0vfs6hgd1np",
             "amount": "100.0",
             "currency": "USD",
             "owner": "rztds0vfs6hgd1np"
         },
         "signatories": [
             "rztds0vfs6hgd1np"
         ],
         "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:Iou"
     },
     "status": 200
 }

Contract Not Found Response

{
     "status": 200,
     "result": null
 }

The following will lookup contracts with contractKey:

POST /data/:ledgerId/v1/fetch HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application/json
 {
     "templateId": "Account:Account",
     "key": [
         "Alice",
         "abc123"
     ]
 }

Contract Found Response

{
     "status": 200,
     "result": {
         "observers": [],
         "agreementText": "",
         "payload": {
             "owner": "Alice",
             "number": "abc123",
             "status": {
                 "tag": "Enabled",
                 "value": "2020-01-01T00:00:01Z"
             }
         },
         "signatories": [
             "Alice"
         ],
         "key": {
             "_1": "Alice",
             "_2": "abc123"
         },
         "contractId": "#697:0",
         "templateId": "11c8f3ace75868d28136adc5cfc1de265a9ee5ad73fe8f2db97510e3631096a2:Account:Account"
     }
 }

Contract Not Found Response

{
     "status": 200,
     "result": null
 }

Writing Data to the Ledger

POST /data/:ledgerId/v1/create

Parameter Description
templateId TemplateId JsObject containing moduleName and entityName
payload JsObject containing list of key-value pairs defined in the target template's fields

request for Create:

POST /data/:ledgerId/v1/create HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application/json
 {
   "templateId": "Iou:Iou",
   "payload": {
     "observers": [],
     "issuer": "rztds0vfs6hgd1np",
     "amount": "999.99",
     "currency": "USD",
     "owner": "rztds0vfs6hgd1np"
   }
 }

HTTP Success response

{
     "result": {
         "agreementText": "",
         "contractId": "#474:0",
         "observers": [],
         "payload": {
             "observers": [],
             "issuer": "rztds0vfs6hgd1np",
             "amount": "999.99",
             "currency": "USD",
             "owner": "rztds0vfs6hgd1np"
         },
         "signatories": [
             "rztds0vfs6hgd1np"
         ],
         "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:Iou"
     },
     "status": 200
 }

HTTP Error response

{
     "status": 400,
     "errors": [
         "JsonError: lookupLfIdentifier: PackageService input error: Cannot resolve templateId: TemplateId(None,Iou,Iou_Foo)"
     ]
 }

POST /data/:ledgerId/v1/exercise

Parameter Description
templateId TemplateId JsObject containing moduleName and entityName
contractId contractId of the target contract
choice Name of the choice to exercise
argument JsObject containing list of key-value pairs to the choice. if the choice does not accept any arguments then an empty object must be used.

request for exercise by contractId:

POST /data/:ledgerId/v1/exercise HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application/json
 {
     "templateId": "Iou:Iou",
     "contractId": "#474:0",
     "choice": "Iou_Transfer",
     "argument": {
         "newOwner": "Alice"
     }
 }

HTTP Success Response

{
     "result": {
         "events": [
             {
                 "archived": {
                     "contractId": "#474:0",
                     "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:Iou"
                 }
             },
             {
                 "created": {
                     "agreementText": "",
                     "contractId": "#475:1",
                     "observers": [
                         "kx8tm3t10cxlapn5"
                     ],
                     "payload": {
                         "iou": {
                             "observers": [],
                             "issuer": "rztds0vfs6hgd1np",
                             "amount": "999.99",
                             "currency": "USD",
                             "owner": "rztds0vfs6hgd1np"
                         },
                         "newOwner": "kx8tm3t10cxlapn5"
                     },
                     "signatories": [
                         "rztds0vfs6hgd1np"
                     ],
                     "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:IouTransfer"
                 }
             }
         ],
         "exerciseResult": "#475:1"
     },
     "status": 200
 }

HTTP Error Response

{
    "status": 500,
    "errors": [
        "INVALID_ARGUMENT: Command interpretation error in LF-DAMLe: dependency error: couldn't find contract AbsoluteContractId(#21:0). Details: N/A."
    ]
}

Request for exercise by Contract Key:

POST /data/:ledgerId/command/exercise HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application/json
 {
     "templateId": "Account:Account",
     "key": ["Alice", "abc123"],
     "choice": "Archive",
     "argument": {}
 }

HTTP Success Response

{
      "status": 200,
      "result": {
          "exerciseResult": "#201:1",
          "events": [
              {
                  "archived": {
                      "contractId": "#124:0",
                      "templateId": "11c8f3ace75868d28136adc5cfc1de265a9ee5ad73fe8f2db97510e3631096a2:Iou:Iou"
                  }
              },
              {
                  "created": {
                      "observers": [],
                      "agreementText": "",
                      "payload": {
                          "iou": {
                              "observers": [],
                              "issuer": "Alice",
                              "amount": "999.99",
                              "currency": "USD",
                              "owner": "Alice"
                          },
                          "newOwner": "Alice"
                      },
                      "signatories": [
                          "Alice"
                      ],
                      "contractId": "#201:1",
                      "templateId": "11c8f3ace75868d28136adc5cfc1de265a9ee5ad73fe8f2db97510e3631096a2:Iou:IouTransfer"
                  }
              }
          ]
      }
  }

POST /data/:ledgerId/v1/create-and-exercise

Parameter Description
templateId TemplateId JsObject containing moduleName and entityName
payload JsObject containing list of key-value pairs defined in the target template's fields
choice Name of the choice to exercise
argument JsObject containing list of key-value pairs to the choice. if the choice does not accept any arguments then an empty object must be used.

HTTP POST request for Create:

POST /data/:ledgerId/create-and-exercise HTTP/1.1
 Host: api.projectdabl.com
 Authorization: Bearer YOUR_TOKEN
 Content-Type: application.json
 {
   "templateId": "Iou:Iou",
   "payload": {
     "observers": [],
     "issuer": "dz1ys3yl4g41e1m2",
     "amount": "999.99",
     "currency": "USD",
     "owner": "dz1ys3yl4g41e1m2"
   },
   "choice": "Iou_Transfer",
   "argument": {
     "newOwner": "lopiw5orw840lr01"
   }
 }

Http Success Response

{
    "result": {
        "events": [
            {
                "created": {
                    "agreementText": "",
                    "contractId": "#190:0",
                    "observers": [],
                    "payload": {
                        "observers": [],
                        "issuer": "dz1ys3yl4g41e1m2",
                        "amount": "999.99",
                        "currency": "USD",
                        "owner": "dz1ys3yl4g41e1m2"
                    },
                    "signatories": [
                        "dz1ys3yl4g41e1m2"
                    ],
                    "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:Iou"
                }
            },
            {
                "archived": {
                    "contractId": "#190:0",
                    "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:Iou"
                }
            },
            {
                "created": {
                    "agreementText": "",
                    "contractId": "#190:2",
                    "observers": [
                        "lopiw5orw840lr01"
                    ],
                    "payload": {
                        "iou": {
                            "observers": [],
                            "issuer": "dz1ys3yl4g41e1m2",
                            "amount": "999.99",
                            "currency": "USD",
                            "owner": "dz1ys3yl4g41e1m2"
                        },
                        "newOwner": "lopiw5orw840lr01"
                    },
                    "signatories": [
                        "dz1ys3yl4g41e1m2"
                    ],
                    "templateId": "ec4ff7a9c2adb530faf269fac0678b700aa0f99faa7624f3fdb97536b544830f:Iou:IouTransfer"
                }
            }
        ],
        "exerciseResult": "#190:2"
    },
    "status": 200
}

Streaming Data from the Ledger

Get all contracts for a party continuously

Streaming /data/:ledgerId/v1/stream/query

See how to pass a token with websockets here.

Stream Query Parameters

  • URL: /v1/stream/query
  • Scheme: ws
  • Protocol: WebSocket

Lists currently active contracts that match a given query, with continuous updates.

A body of content type application/json must be sent first, formatted according to contract search query doc.

{"templateIds": ["Iou:Iou"]}

Multiple queries may be specified in an array, for overlapping or different sets of template IDs:

[
     {"templateIds": ["Iou:Iou"], "query": {"amount": {"%lte": 50}}},
     {"templateIds": ["Iou:Iou"], "query": {"amount": {"%gt": 50}}},
     {"templateIds": ["Iou:Iou"]}
 ]

Output is a series of JSON documents, each payload formatted according to contract search query doc.

{
     "events": [
         {
             "created": {
                 "observers": [],
                 "agreementText": "",
                 "payload": {
                     "observers": [],
                     "issuer": "Alice",
                     "amount": "999.99",
                     "currency": "USD",
                     "owner": "Alice"
                 },
                 "signatories": [
                     "Alice"
                 ],
                 "contractId": "#1:0",
                 "templateId": "b70bbfbc77a4790f66d4840cb19f657dd20848f5e2f64e39ad404a6cbd98cf75:Iou:Iou"
             },
             "matchedQueries": [
                 1,
                 2
             ]
         }
     ]
 }

Upstream documentation is here.

Streaming /data/:ledgerId/v1/stream/fetch

See how pass a token with websockets here.

Stream Fetch Parameters

  • URL: /v1/stream/fetch
  • Scheme: ws
  • Protocol: WebSocket

List currently active contracts that match one of the given {templateId, key} pairs, with continuous updates.

application/json body must be sent first, formatted according to the following rule:

[
     {"templateId": "<template ID 1>", "key": <key 1>},
     {"templateId": "<template ID 2>", "key": <key 2>},
     ...
     {"templateId": "<template ID N>", "key": <key N>}
 ]

Where:

  • templateId -- contract template identifier, same as in :ref:create request <create-request>,
  • key -- contract key, formatted according to the :doc:lf-value-specification,

Example:

[
     {"templateId": "Account:Account", "key": {"_1": "Alice", "_2": "abc123"}},
     {"templateId": "Account:Account", "key": {"_1": "Alice", "_2": "def345"}}
 ]

The output stream has the same format as the output from the Contracts Query Stream_. We further guarantee that for every archived event appearing on the stream there has been a matching created event earlier in the stream.

Upstream documentation here.

Onboarding authenticated users to your Ledger

Almost every app that is deployed in DABL is meant to be used by multiple users and not just you, its creator.

Each of those users must sign up with their own account in order to be able to act as a DAML party on a ledger. This provisioning happens automatically and in an ad-hoc fashion when the user signs up for the first time. What is more, each new party that enters the system needs to get properly onboarded in your DAML app so that it gets the appropriate disclosures and permissions to perform actions.

This section outlines the best practices and patterns for onboarding newly registered users to your DAML app, by making reference to DABL's reference application, DABL Chat. You can find the source code for DABL Chat here, for an up to date and working example that incorporates these ideas.

The Login In with DABL button

Screenshot Placeholder

This is a button that redirects the new user to sign up with their email and password, or other supported identity provider, and then back to your app, while also allocating a ledger identity for them. This process is even more straightforward for users that already have a DABL account. The login button is essentially an html <a> tag with a hypertext reference to a login url.

<a href={`https://login.projectdabl.com/auth/login?ledgerId=${yourLedgerId}`}>Log In with DABL</a>

Where yourLedgerId is the ledger Id of the deployed ledger for your app. This ledger id can be found in the Ledger Settings page. Since this id is not known until the ledger is created, but the ledger id is present in the URL where your application is served, you can fetch it dynamically and form the url using this handy javascript function:

const makeLoginUrl = () => {
   let host = window.location.host.split('.');
   const ledgerId = host[0];
   let loginUrl = host.slice(1);
   loginUrl.unshift('login');

   return loginUrl.join('.')
     + (window.location.port ? ':' + window.location.port : '')
     + '/auth/login?ledgerId=' + ledgerId;
 }

A successful user authentication will redirect the user to the app url. This url will carry two extra query parameters: party and token.

https://yourLedgerId.projectdabl.com/?party=theUsersParty&token=theUsersJWT

Your frontend can then extract those parameters and store them in local storage so it can retrieve them later to make authenticated calls:

const urlParams = new URLSearchParams(url.search);
 const party = urlParams.get('party');
 localStorage.setItem('party', party);
 const token = urlParams.get('token');
 localStorage.setItem('token', token);

Listening for new users

When a user registers in your ledger, their name will appear in the Parties list under the Live Data tab. This is because of automatic disclosure of the existence of the Ledger Party to the person that operates the ledger.

Screenshot Placeholder

Your frontend can then create a contract that looks something like this:

template UserSession
   with
     operator : Party
     user : Party
     userName : Text
   where
     signatory user
     key user : Party
     maintainer key

     controller operator can
       UserSessionAck: ...
         do
           -- Things required to onboard your new user (or simply return the original contract)
           -- ...

This template requires three pieces of information:

  1. The UserAdmin Party which is plays the role of the operator in DABLChat. Once you have deployed a website for your application, you can get the UserAdmin Party at https://${yourLedgerId}.projectdabl.com/.well-known/dabl.json, which is documented below.
  2. The user Party. This is available alongside the query parameter as listed above.
  3. The party's userName (optional). DABLChat uses the name from the token.

After a user logs in to a ledger and creates this contract, the contract gets disclosed to your UserAdmin party. All you have to do from there is to run automation as UserAdmin and listen to creations of your contract (Chat.UserSession in this example). When such a contract gets created, your bot will read the user's Party from the contract and kick off an onboarding workflow for it.

Your automation callback could look something like this:

@client.ledger_created('Chat.UserSession')
 def invite_user_to_chat(event):
     return exercise(event.cid, 'UserSessionAck')

NOTE: this example uses decorators defined in the DAZL client, a python client for DAML ledger actions. For documentation on DAZL, see this page.

You can configure the a bot like this with a few clicks from the Automation tab:

Screenshot Placeholder

This bot will accept all UserSessions that it sees, without additional verification. You could put more sophisticated logic here if you want to prevent certain logins from succeeding.

template Operator
   with
     operator : Party
   where
     signatory operator

     key operator : Party
     maintainer key

     controller operator can
       nonconsuming InviteUser : ContractId UserInvitation
         with
           user : Party
           userName : Text
         do
           create UserInvitation with ..

This is an instance of a typical “Initiate - Accept” pattern that is further discussed in the official DAML docs. But how to create the Operator contract in the first place? This can be done either manually in the DABL console or more conveniently by adding an extra callback function to your bot:

@user_admin.ledger_ready()
 def create_operator(event):
     logging.info('The User Admin bot is ready')
     res = user_admin.find_active('MyApp.Operator')
     logging.info(f'found {len(res)} MyApp.Operator contracts')

     if not res:
         logging.info(f'Creating Operator contract for {user_admin.party}...')
         return client.submit_create('MyApp.Operator', { 'operator': user_admin.party })
     else:
         logging.info(f'Operator {user_admin.party} is ready')

Similar to the user_admin.ledger_created decorator, user_admin.ledger_ready will fire once when the bot has properly initialized. The create operator function will first search for any active contracts of template MyApp:Operator and, if none exist - which will be the case if this gets deployed for the first time - it will go ahead and create one. Otherwise it will just print another log announcing that it is ready for requests.

Mapping unique party ids to human-readable names

DABL does not give you control over the Party ids of your users on the ledger. When a user logs in to your app, and therefore registers as a party on your ledger, DABL assigns a unique id to that party of the form ledger-party-<a unique id>. This is different than what you might be used to when creating a local scenario for your DAML model, where you may assign human-readable names to the Party fields of your templates. In a real world scenario Party identifiers can be thought of as public addresses in a public blockchain that carry no information about the actual user behind them.

With that in mind, you will need to decide where to store the mappings between the Party identifiers and the human-readable names (of type Text) for your users, should you need to display them on your app. You also need to decide what type of party discovery mechanism you want for your app. Keep in mind that DAML is modelling distributed workflows and will only disclose contracts to parties on a need-to-know basis depending on your model. DABL stays true to that, but offers you the ability to centralize your party discovery by leveraging the UserAdmin and Public parties under the well known endpoint (https://${yourLedgerId}.projectdabl.com/.well-known/dabl.json).

Should you need human-readable names on your app, you can store those on instances of a fit-for-purpose DAML template that you model, containing at least a party : Party field and partyName : Text field. To make those publicly available to other users of your app, you can optionally add the Public Party as an observer. To selectively disclose the mappings to other users, you can proxy them through a bot running as the UserAdmin party after adding UserAdmin as an observer.

As an example, DABL Chat stores the human-readable name mapping on the User contract itself:

template User
   with
     operator : Party
     user : Party  -- user's party identifier
     publicParty : Party
     userName : Text  -- user's human-readable name
   where
     signatory operator, user

     -- ...

It also allows for users to publish a human-readable name of their preference to other users by leveraging the Public Party:

template SelfAlias
   with
     operator : Party
     publicParty : Party
     user : Party  -- user's party identifier
     alias : Text  -- user's preferred public name
   where
     signatory operator, user

     observer publicParty  -- the public party is an observer

     -- ...

GET :ledgerId.projectdabl.com/.well-known/dabl.json

Returns a json object of the form:

{
   "userAdminParty": "ledger-party-[uuid]",
   "publicParty": "public-[:ledgerId]"
 }

This can be used to discover the ledger party which is automatically allocated for the ledger administrator, which is automatically notified about people joining the ledger. Additionally, it includes the party id of the public party, for which unauthenticated requesters may request and receive a valid access token.

Service Accounts

Authenticating programmatic requesters

For those instances where you'd like to authenticate requests to programs, instead of people, DABL supports service accounts. These might be used when building an application that is composed of self hosted autonomous ledger accessing components. In these cases, if you simply copy your console access tokens into your application configuration, they will expire after 24 hours, quickly leading to failures authenticating your requests. For this reason, DABL also supports service accounts, which take the form of a long-lived credential which can be exchanged for an access token, which is itself valid for a longer period of time.

To allocate yourself a service account, navigate to the ledger settings page. On that page, you can click the button labeled "add service account", which will issue you a service account for that ledger, which can then be used to request access tokens. Be sure to copy the credential and store it in a secure location, as it will only be revealed exactly one time, and subsequent attempts to retrieve it will not succeed.

POST http://login.projectdabl.com/sa/login

This page presents a login dialog which accepts credentials over basic auth. You can post to it using a credential id, and a generated credential. This will return you a JWT which is otherwise indistinguishable from a console access token, except that it will refer to a party with the "sa" prefix, for service account. Now, you can store your generated credential pair within your application configuration, and deploy your very own authenticated ledger automaton!

Public Party

These are the API endpoints that can be used to support ledger access before any user has logged in. Even though DABL provides a mechanism for automatically allocating a ledger identity for an authenticated requester, there are also cases where it's important to be able to read ledger data before this, for instance to read public catalog data. In order to support this, DABL designates a party with a reserved name, which requesters can obtain a JWT to make requests with, but without supplying authentication credentials.

POST api.projectdabl.com/api/ledger/:ledgerId/public/token

This can be used by any requester, in order to obtain a token with which to perform read only authenticated ledger activities. Application authors should take care to not disclose data to the public party which is not actually public. See the documentation for the .well-known endpoint in order to undertand how this party could be automatically discovered by an application.