Skip to content

Make Requests to Domino

Info

This how-to assumes you are familiar with setting up a project either manually or via VoltScript Dependency Management. If you are not, follow the Intro to VoltScript Tutorials.

Introduction

When coding LotusScript, Domino access is expected and set up automatically via NotesSession object and specific user access automatically applied. VoltScript is middleware and may or may not require access to Domino data to perform its middleware functions. So the relevant VoltScript Extension, DrapiVSE, needs to be added, as for any other VoltScript extension. DrapiVSE allows you to easily work with the Domino Rest API, and the classes and methods it uses map to corresponding endpoints in the Domino Rest API.

VoltScript dependencies

Incorporating DrapiVSE is straightforward. You just need to add the following JSON object to the vsesDependencies element in your atlas.json.

        "DrapiVSE": {
            "library": "DrapiVSE VoltScript Extension",
            "version": "1.0.0",
            "module": "drapivse",
            "repository":"volt-mx-marketplace"
        }

You'll need to add to your repositories object in the atlas.json of your project:

{
    "id": "volt-mx-marketplace",
    "type": "marketplace",
    "url": "https://community.demo-hclvoltmx.com/marketplace"
}

To use the extension in your script, enter UseVSE "*DrapiVSE".

You will probably also want to incorporate JsonVSE.

Domino REST API

DrapiVSE is designed to access Domino REST API. The VoltScript Extension is only designed for data access, not database or user management. So only the data APIs are exposed in DrapiVSE. Of course, this doesn't preclude you making REST calls via WebVSE to other Domino REST API endpoints.

Info

This how-to guide assumes familiarity with Domino REST API terminology. For more information on the Domino REST API terminology, see the Domino REST API documentation

It's important to bear in mind performance because the VoltScript code will be running as middleware, making individual REST service calls for most APIs. The following diagrams demonstrate REST service calls for creating a document and updating an existing document:

DrapiVSE and Domino REST API: Create Document

sequenceDiagram
participant DrapiVSE
participant DRAPI as Domino REST API
DrapiVSE ->> DRAPI: DrapiServer.login()
DRAPI ->> DrapiVSE: Bearer token, server info, etc
DrapiVSE ->> DrapiVSE: DrapiServer.createRequest("demo")
DrapiVSE ->> DrapiVSE: DrapiRequest.createDocument()
DRAPI ->> DrapiVSE: DrapiResponse object or error

DrapiVSE and Domino REST API: Update Document

sequenceDiagram
participant DrapiVSE
participant DRAPI as Domino REST API
DrapiVSE ->> DRAPI: DrapiServer.login()
DRAPI ->> DrapiVSE: Bearer token, server info, etc
DrapiVSE ->> DrapiVSE: DrapiServer.createRequest("demo")
DrapiVSE ->> DRAPI: DrapiRequest.putDocument() / <br/>DrapiRequest.patchDocument()
DRAPI ->> DrapiVSE: DrapiResponse / DrapiDocument object or error

Tip

A good understanding of Domino REST API is important to use DrapiVSE at its best. Coding using DrapiVSE in the same you would code using Notes classes will be difficult, as DrapiVSE closely corresponds with the DRAPI endpoints, and you will not get an optimal outcome. Think of it as working with a Rest API, not with the Domino API.

DrapiServer class

The DrapiServer class is the entrypoint for any Domino REST API calls. You need to specify the serverURL, which is protocol + server name + "api/v1". This is the same URL that appears under "Servers" on the Swagger REST API:

Domino REST API Server

After adding the serverURL you will need to call login() passing a username and password. The following code will login to the server, assuming the variables serverName, userName and password have been set.

Function login(server as DrapiServer, userName as String, password as String)
    server.serverURL = SERVER_NAME
    Call server.login(userName, password)
End Function

This will give a logged in session to the Domino REST API as the passed user, which can be verified with server.connected or by retrieving the token with server.JWTToken.

Passing an Existing Token

Best practice is to log in once and re-use a valid JWT token for subsequent requests, serialized by whatever is calling the VoltScript code. DrapiServer supports the ability to pass an existing token, server.JWTToken is read-write, not read-only, so can be set. When doing so, you do not need to run Call server.login() again, the valid token will just be passed with the relevant DrapiRequest. However, server.connected will be false - this is only set when calling .login().

Warning

You will need to ensure you handle a token that has expired. This may require recreating the DrapiRequest after updating the DrapiServer instance.

Tip

If you are not storing the token for future use, you can logout at the end of the code. This will invalidate the JWT token and prevent it being re-used. This can be done with Try/Catch/Finally:

    Try
        'Login and run your code
    Catch
        Print "Error " & Error() & " on line " & Erl()
    Finally
        If (Not server is Nothing && server.connected) Then server.logout
    End Try

DrapiRequest class

The DrapiRequest class is the entrypoint for data operations on a specific Domino REST API scope, including accessing list (view) and document data, and performing DQL queries. All requests except getServerInfo() require you to pass the relevant scope when creating the request.

Once you have a DrapiRequest object then any method called against it will return a DrapiResponse object. You use DrapiRequest to work with objects such as lists (i.e. Domino views and folders), documents, profiles, and so on.

You can use DrapiResponse.ResponseCode to determine if the request was processed correctly (it will have a ResponseCode of 200). Then you can use DrapiResponse.ContentBody to access the JSON response content of your request.

Note

You will probably want to use JsonVSE to more easily parse the JSON content from Domino REST API.

Tip

When creating a DrapiRequest, calls can be chained, for example Set serverInfo = server.createRequest("").getServerInfo().

Accessing list / view data

One of the primary entrypoints to document data is Domino views, or in the Domino REST API terminology "lists". DrapiVSE retains Domino REST API's terminology. Typically the developer will know the name of the list to use, but if not then the developer can use DrapiRequest.getLists() to get a list of available/configured lists, or a subset thereof.

There are two functions available for accessing view data:

  • getListEntries() for accessing view entries or documents based on view entries. This is also used to get a subset of entries/documents based on one or more key(s), like getViewEntriesByKey().
  • getListPivot() for retrieving view data as a pivot table. Computes min, max, count, total of retrieved view entries.

Since the getListEntries() method is capable of retrieving subsets of documents based on a variety of parameters, a special arguments object is needed to contain all of the parameters passed to getListEntries(). This object is based on the GetListEntriesArgs class.

The following code will get the first 5 entries from a view. It is assumed that the DrapiServer is already logged in:

Function getEntries(server as DrapiServer, scopeName as String, viewName as String) as JsonObject
    Dim request as DrapiRequest, response as DrapiResponse
    Dim entryArgs as New GetListEntriesArgs
    Dim parser as New JsonParser, jobj as JsonObject

    Set request = server.createRequest(scopeName)

    entryArgs.mode = "default"
    entryArgs.RichTextAs = "html"
    entryArgs.Count = 5

    Set response = request.getListEntries(viewName, entryArgs)

    If response.ResponseCode = 200 Then
        Call parser.loadFromJSON(response.ContentBody)
        Set jobj = parser.getRootObject
    End If

    return jobj
End Function

Tip

CRUD in web and mobile applications work best when providing targeted access to a small set of data. For this reason, DQL is a good approach when building your user experience.

Basic CRUD Operations

Creation

Creating a document requires knowing that a form is exposed and the the items expected for the "default" mode, which is the mode used when creating a document. The developer should know this, it is available by checking the schema for the specific application. The following code will create a document and save it to Domino REST API:

Note

DrapiDocument is an extension of DrapiResponse, which means properties such as ResponseCode, ContentBody, and so on are available as well as the DrapiDocument properties and methods.

Function createCustomer(server as DrapiServer, scopeName as String) as DrapiDocument
    Dim request as DrapiRequest, cdoc as DrapiDocument
    Dim jobj as New JsonObject

    Set request = server.createRequest(scopeName)
    Call jobj.insertValue("Form", "Customer")
    Call jobj.insertValue("Color", "Red")
    Call jobj.insertValue("first_name", "John")
    Call jobj.insertValue("last_name", "Doe")
    Call jobj.insertValue("gender", "Male")
    Call jobj.insertValue("Pet", "Cockapoo")
    Set cdoc = request.createDocument(jobj.toString(False))

    If cdoc.ResponseCode = 200 Then
        Print "Customer created with ID: " & cdoc.UNID
    Else
        Print "Error creating customer: code: " & cdoc.ResponseCode & ", message: " & cdoc.ErrorMessage
    End If

    return cdoc
End Function

Note

DrapiRequest.createDocument() can take two additional arguments, a rich text format (default is html) and a parent UNID. When creating a new document, if you provide a parent UNID, the document will be created as a response to the given parent document.

You can also create multiple documents at once, in bulk, using DrapiRequest.bulkCreateDocuments(). The example below assumes that docsjson is a String containing an array of json objects representing the documents to be created.

Sub createCustomers(server as DrapiServer, scopeName as String, docsjson as String)
    Dim request as DrapiRequest, response as DrapiResponse

    Set request = server.createRequest(scopeName)
    Set response = request.bulkCreateDocuments(docsjson)
    If response.ResponseCode = 200 Then
        Print "Customers created successfully."
    Else
        Print "Error creating customers: code: " & response.ResponseCode & ", message: " & response.ErrorMessage
    End If
End Sub

Form Access Modes

Domino REST API uses different Form Access Modes for managing what fields are available for read / update at particular stages in the workflow. Consequently, it's important to know the Form Access Modes available and you will need to specify the required Form Access Mode when updating the document. DrapiRequest.GetDocumentModes() will provide the Form Access Modes and fields that can be edited when passing an UNID:

Function getDocModes(server as DrapiServer, scopeName as String, unid as String) as JsonObject
    Dim request as DrapiRequest, response as DrapiResponse
    Dim parser as New JsonParser, jobj as JsonObject
    Set request = server.createRequest(scopeName)
    Set response = request.getDocumentModes(unid)
    If response.ResponseCode = 200 Then
        Call parser.loadFromJSON(response.ContentBody)
        Set jobj = parser.getRootObject
    End If
    return jobj
End Function

Retrieval

Retrieving a document is done using DrapiRequest.getDocument(), which returns a DrapiDocument object. The third parameter is the form access mode.

Function getDoc(server as DrapiServer, scopeName as String, docID as String) as DrapiDocument
    Dim request as DrapiRequest, doc as DrapiDocument
    Set request = server.createRequest(scopeName)
    Set doc = request.getDocument(docID, "html", "default")
    Return doc
End Function

Warning

Exposing rich text content to editing outside Notes Client can be risky. Different rich-text editors have different abilities to accept / output richly-formatted content. So even with no changes, there are no guarantees the user interface will faithfully round-trip the content outputted. Outputting as HTML means the code retains whatever styling used when creating the content. This may mean the outputted style does not fit naturally with the rest of the web application.

Update

To update the document, you can use a variety of methods, including:

  • You could get the document in a DrapiDocument object, replace the JSON in DrapiDocument.JSONValue and then call DrapiDocument.put(), with the relevant Form Access Mode.
  • You could use DrapiRequest.putDocument() to replace all of the document JSON content.
  • You could use DrapiRequest.patchDocument() to just replace one or more field values in the given document.

The example below uses DrapiRequest.patchDocument() to update the Color to Red, and then returns the document's contents as a DrapiDocument.

Function updateDoc(server as DrapiServer, scopeName as String, unid as String) as DrapiDocument
    Dim request as DrapiRequest, doc as DrapiDocument
    Dim jobj as New JsonObject
    Set request = server.createRequest(scopeName)
    Call jobj.insertValue("Color", "Red")
    Set doc = request.patchDocument(unid, jobj.toString(False), "html", "default")
    Return doc
End Function

Delete

Deleting a document can be done by passing either the unid to DrapiRequest.deleteDocument or by getting a DrapiDocument object and calling DrapiDocument.Delete(). Both require passing a Form Access Mode name in which the current user has permissions to delete documents.

Warning

If the deletion isn't successful, a 400 error will be returned in the ResponseCode, with further details in the ErrorMessage.

The following code will delete a document using just its UNID:

Sub deleteDocumentByUNID(server as DrapiServer, scopeName as String, unid as String)
    Dim request as DrapiRequest, response as DrapiResponse
    Set request = server.createRequest(scopeName)
    Set response = request.deleteDocument(unid, "default")
    If response.ResponseCode = 200 Then
        Print "Document with UNID " & unid & " deleted successfully."
    Else
        Print "Error deleting document: code: " & response.ResponseCode & ", message: " & response.ErrorMessage
    End If
End Sub

There may be occasions where you want to act upon a document first, either checking item values or copying it somewhere, for example to an archive. The following code will retrieve a document, create a copy, delete the original, and return the new document:

Function copyAndDeleteDocument(server as DrapiServer, scopeName as String, unid as String) as DrapiDocument
    Dim request as DrapiRequest, response as DrapiResponse
    Dim doc as DrapiDocument, newdoc as DrapiDocument
    Set request = server.createRequest(scopeName)
    Set doc = request.getDocument(unid, "default")
    If doc.ResponseCode = 200 Then
        Set newdoc = request.createDocument(doc.JsonValue)
        If newdoc.ResponseCode = 200 Then
            Set response = request.deleteDocument(unid, "default")
        End If
    End If
    return newdoc
End Function

Bulk CRUD operations and DQL

Bulk operations are done from DrapiRequest, which returns a DrapiResponse object. You can then access the DrapiResponse.ResponseCode to check on the success of your call (returns a 200 if successful), .ContentBody to access the actual JSON content returned, or .ErrorMessage if there was a problem.

Overall, bulk operations are more efficient because it will make a single REST service call to Domino REST API instead of one REST service call for each document.

There are five Bulk operations available in DrapiRequest:

  • BulkGetDocuments - retrieves one or more documents based on the UNIDs provided in a String Array
  • BulkCreateDocuments - creates one or more documents from the JSON array provided as a String
  • BulkFolderDocuments - adds or removes one or more documents based on the UNIDs provided in a String Array
  • BulkPatchDocuments - updates one or more field values provided in a serialized JSON array, based on the documents identified by the provided DQLQuery
  • BulkDeleteDocuments - deletes one or more documents based on the UNIDs provided in a String Array

Retrieval by UNIDs

If you have a list of UNIDs for documents, you can use DrapiRequest.bulkGetDocuments() to retrieve them. The following code will take an array of UNID strings and return an array of JSON objects, where each string corresponds to the document JSON.

Function getDocuments(server as DrapiServer, scopeName as String, unids() as String) as JsonObject
    Dim request as DrapiRequest, response as DrapiResponse
    Dim parser as New JsonParser, jobj as JsonObject
    Set request = server.createRequest(scopeName)
    Set response = request.bulkGetDocuments(unids, "html", "default", True)
    If response.ResponseCode = 200 Then
        Call parser.loadFromJSON(response.ContentBody)
        Set jobj = parser.getRootObject
    End If
    return jobj
End Function

Retrieval via DQL

DQL provides the ability to return documents based on a query. The following code would return the first five Customer documents where the color is "Blue":

Function performDqlQuery(server as DrapiServer, scopeName as String) as JsonObject
    Dim request as DrapiRequest, response as DrapiResponse
    Dim parser as New JsonParser(), jsonObj as JsonObject
    Dim query as New JsonObject
    Dim vars as New JsonObject

    Set request = server.CreateRequest(scopeName)

    Call query.insertValue("maxScanDocs", 5000)
    Call query.insertValue("maxScanEntries", 2000)
    Call query.insertValue("mode", "default")
    Call query.insertValue("noViews", False)
    Call query.insertValue("timeoutSecs", 300)
    Call query.insertValue("query", "form = 'Customer' and Color = ?Color")
    Call query.insertValue("viewRefresh", False)
    Set vars = new JsonObject()
    vars.label = "variables"
    Call vars.insertValue("Color", "Blue")
    Call query.insertObject(vars)

    Set response = request.dqlQuery(query.toString(True), "execute", 5)
    If response.ResponseCode = 200 Then
        Call parser.loadFromJSON(response.ContentBody)
        Set jsonObj = parser.getRootObject()
    Else
        Print "Error performing DQL query: " & response.ResponseCode & " - " & response.ErrorMessage
    End If
    Return jsonObj
End Function

Tip

Use JsonVSE to build the query rather than trying to build it as a String. If you get an error message for invalid JSON, print out the JSON and cross-reference it with / try it in Swagger on the relevant server.

Note

DrapiRequest.DQLQuery() can also be used to perform other DQL verbs - "query" and "explain".

Update via DQL

Bulk update is performed via a DQL query, a list of items to update and a Form Access Mode with which to perform the update. The DrapiRequest.bulkPatchDocuments() method can either return the full JSON of the documents at the relevant mode, or a JSON object that contains a summary of the documents updated. The following code will update Customers where Color is "Red", changing the color to "Maroon". It will return all values from the underlying documents.

Function updateDocuments(server as DrapiServer, scopeName as String) as String
    Dim request as DrapiRequest, response as DrapiResponse
    dim query as String
    Dim replaceItems as New JsonObject()

    Set request = server.CreateRequest(scopeName)

    Call replaceItems.insertValue("Color", "Maroon")
    query = "form = 'Customer' and Color = 'Red'"

    Set response = request.bulkPatchDocuments(query, replaceItems.toString(False), "html", "default", 5000, True, True)

    If response.ResponseCode = 200 Then
        Return response.ContentBody
    Else
        Print "Error updating documents: " & response.ResponseCode & " - " & response.ErrorMessage
        Return ""
    End If
End Function

If False had been passed as the last parameter of DrapiRequest.bulkPatchDocuments() the result would have looked something like this:

[
{"statusText":"OK","status":200,"message":"Document 2B057308D23ED5AD00258A210046664B updated","unid":"2B057308D23ED5AD00258A210046664B"},
{"statusText":"OK","status":200,"message":"Document A1057308D23ED5AD00258A210046527F updated","unid":"A1057308D23ED5AD00258A210046527F"}
]

Deletion by UNIDs

Just like DrapiRequest.bulkGetDocuments(), you can use an array of UNIDs to delete multiple documents at once.

The following example will delete the documents indicated in the UNID string array, and return a status message for each document:

Function deleteDocuments(server as DrapiServer, scopeName as String, unids() as String) as JsonObject
    Dim request as DrapiRequest, response as DrapiResponse
    Dim parser as New JsonParser, jobj as JsonObject
    Set request = server.createRequest(scopeName)
    Set response = request.bulkDeleteDocuments(unids, "default")
    If response.ResponseCode = 200 Then
        Call parser.loadFromJSON(response.ContentBody)
        Set jobj = parser.getRootObject
    End If
    return jobj
End Function

Attachments

Add an attachment

Adding an attachment is done by using DrapiRequest.createAttachment(). You need to pass the filepath of the attachment to add and an optional name of a field in which to place the attachment.

The following code will create an attachment in a field called "RichText" in the provided DrapiDocument:

Note

You could bypass DrapiDocument altogether if you already know the UNID of the target document by simply calling DrapiRequest.createAttachment() directly.

Function addAttachment(doc as DrapiDocument, filePath as String) as String
    Dim request as DrapiRequest, response as DrapiResponse
    Set request = doc.ParentRequest
    If filepath = "" Then
        filePath = CurDir() & "/libs/functions.vss"
    End If
    Set response = request.createAttachment(doc.Unid, filePath, "RichText")
    If response.ResponseCode = 200 Then
        Return response.ContentBody
    Else
        Print "Error creating attachment: " & response.ResponseCode & " - " & response.ErrorMessage
        Return ""
    End If
End Function

Download an attachment

Downloading an attachment requires knowledge of the attachment name to download. The following code will download the indicated attachment in the provided document to the indicated targetpath, assuming the Form Access Mode is configured correctly:

Sub getAttachment(doc as DrapiDocument, attachName as String, targetPath as String)
    Dim request as DrapiRequest, response as DrapiResponse
    Dim parser as New JsonParser, jobj as JsonObject
    Set request = doc.ParentRequest
    Set response = request.getAttachment(doc.UNID, attachName, targetPath)
    If response.ResponseCode = 200 Then
        Print "Attachment downloaded successfully to " & targetPath
    Else
        Print "Error downloading attachment: " & response.ResponseCode & ", see target file for error"
    End If
    return jobj
End Sub

Delete an Attachment

Similar to downloading an attachment, deleting an attachment requires knowledge of the attachment name to be deleted. The FieldName parameter indicates the name of the field in which the attachment resides. You should pass this to remove the hotspot for the attachment from that rich text field, otherwise the hotspot will remain. If the attachment is attached directly to the document, not a field, there is not need to provide this parameter.

Function deleteAttachment(doc as DrapiDocument, attachName as String, fieldName as String) as JsonObject
    Dim request as DrapiRequest, response as DrapiResponse
    Dim parser as New JsonParser, jobj as JsonObject
    Set request = doc.ParentRequest
    Set response = request.deleteAttachment(doc.UNID, attachName, fieldName)
    If response.ResponseCode = 200 Then
        Call parser.loadFromJSON(response.ContentBody)
        Set jobj = parser.getRootObject
    End If
    return jobj
End Function

Investigate APIs for more

Other APIs are available for more various other Domino REST API functions, like folder manipulation, profile document handling, agent processing, and use of QRP views. See the API documentation for more details.

The complete implementations of the code snippets are available on GitHub.