BookmarkSubscribeRSS Feed

Using REST API to create and modify SAS Visual Analytics Reports

Started 4 weeks ago by
Modified 4 weeks ago by
Views 341

SAS Viya supports REST API that can help accomplish various tasks from data management to model scoring.

 

As of the Stable 2025.04 release, you can also create and manipulate SAS Visual Analytics reports using REST API. REST API make it:

 

  1. Easy to automate repeated tasks.
  2. Possible to schedule various tasks.

 

In this post, we will learn:

 

  1. how to use SAS Viya REST API
  2. how to create a report while adding a data source
  3. what ETags are
  4. modify data item properties in an existing report
  5. check if an object already exists in a report
  6. how to add objects to an existing report

 

Using SAS Viya REST API

 

You can learn more about SAS Viya REST API on the SAS developers site. Ensure you select the cadence corresponding to the SAS Viya version you have available, and filter for Visualization and Reporting:

 

42818_3_1_REST_API_Overview.png

 

You will notice there are several REST APIs in this category:

 

  • Insights: API provides an endpoints for automated explanation of a variable.
  • Reports: API provides endpoints that return lists of reports, their contents, their ETag, to change report state (for report viewers), and more.
  • Report Transforms: API provides endpoints for report transformations such as changing the report data source, localizing reports and changing the themes of reports.
  • Visual Analytics: API provides endpoints to change the report data source, add or modify data items or objects, change parameter values and export the report.

 

In this post, I will largely focus on the Visual Analytics API.  Click on the Visual Analytics API to see all its endpoints listed by category. In this post, we focus on the endpoints in the Report category.

 

42818_3_2_Visual_Analytics_API.png

 

There are various tabs at the top. I recommend reading the Details tab the first time you use a new API. You won’t need to read through the documentation for Authentication, as long as you submit the code from SAS Studio Flows or if you have already set up authentication to SAS using the SAS extension in Visual Studio Code.

 

Navigate to the Reports category and click on Create a report while applying the specified operation(s):

 

42818_3_3_POST_Request.png

 

Note that this endpoint is a POST request (we will come back to this later). Other requests are GET and PUT. You can learn more about this in the Details tab of the API (highlighted earlier). You can access SAS Viya REST API using Shell, JavaScript, Node, Python, Go, C, C#, Java, HTTP, Kotlin, R and Ruby, as well as SAS’ own PROC HTTP (see post and this YouTube video). In this post, we will focus on Python, using the Requests library. You can also use pure Python 3.

 

Getting Started

 

Let’s get started. First let’s add the data we will use to build our report to CAS. From your preferred editor, either SAS Studio or Visual Studio Code, paste the following code. Note you can also find all the code from this post in this notebook. We load the CLASS table from the SASHELP library into CAS to use it in a report:

 

cas;   
proc casutil;
    droptable casdata="class" incaslib="casuser" quiet;
    load data=sashelp.class outcaslib="casuser" casout="class" promote;
quit;

 

The next step is to put the authentication token and the SAS Viya host URL into macro variables. These are then read into Python variables in the PROC PYTHON and are referenced in each API call. This is the easiest way to authenticate. You will need to rerun this code if your session times out.

 

%let token=%sysget(SAS_CLIENT_TOKEN);

%let host=%sysfunc(getoption(SERVICESBASEURL));

proc python; submit; import pandas as pd import requests token = SAS.symget('token') host = SAS.symget('host')
endsubmit; run;

 

Creating a report while adding a data source

 

We want to create the report and add a data source at the same time. At the top right, select the addData operation.

 

42818_3_4_Request_Body_Structure.png

 

Note that the required structure of the request body with all its options is documented further below. Let us study the structure of the request sample code provided – most requests will follow this structure. The code contains:

 

  • Import statement: to import the requests library,
  • URL: which we need to adapt.
    • We will use the host variable created above for the stem of the URL to point to our SAS Viya Environment instead of com.
    • The URL ends with /visualAnalytics/reports. Generally different endpoints have different URL endings. Some API endpoints allow including path variables to customize the request. In that case, they can be appended to the URL. Simply add a ? after the URL, followed by the name=value pair(s) of parameter(s), separated by &.
  • Report body: which is assigned to the payload variable.
    • In our request, we create a report named “report123” in our My Folder. If a report with that name already exists, we replace it.
    • At the same time as creating the report, we want to add data to the report. We do this using the addData The sample code on the developer.sas.com site, for example, specifies the AIRLINE_ROUTE_NETWORK table from the Public caslib. We will adapt this to our needs.
  • Header: In general, the header includes information on the media type of the request body in the Content Type key of the dictionary.
    • The Authorization key includes the authentication information.
    • The desired response media types are included as a comma separated list in the Accept key. You can find more information on the different types of media types in the API Details tab (highlighted above).
    • We need to adapt the sample code to include the authentication token, which is in the token variable created earlier.
  • a call to the POST function in the requests library, with the URL, header and payload

 

42818_3_5_Operations_Array.png

 

The report body is a Python dictionary, where each parameter is a key and its value is the value in the dictionary. Note the options for resultNameConflict: in case of a naming conflict, you could also choose to abort creating the report, or add a numeric suffix to the report name. Note that operations needs to be an array. Click on operations and select addDataOperationRequest in the drop-down to see the requirements for the addData operation.

 

42818_3_6_addData.png

 

42818_3_7_Nested_Dictionaries.png

 

Note the structure of the response body: we need to specify the server, library and table name of the data source in a dictionary called cas, in a nested dictionary as the value to the key addData, which is an element of the operations array. This is how you can create the necessary code from the documentation - each time we drill in (as shown above), we need to use nested dictionaries in the code. This is a key point in this post. Understanding this will allow you to use any API endpoint based on the documentation.

 

Our sample code is almost ready to use. Lastly, we will wrap it in a PROC PYTHON submit endsubmit block, and include our CAS table, URL host and the authentication token. We will need to adapt the sample code from the documentation more in later examples.

 

proc python;
submit;

reportName = "Report123"
url = str(host) + "/visualAnalytics/reports"  
payload = {
    "resultFolder": "/folders/folders/@myFolder",
    "resultReportName": reportName,
    "resultNameConflict": "replace",
    "operations": [{ "addData": { "cas": {
                    "server": "cas-shared-default",
                    "library": "CASUSER",
                    "table": "CLASS"
                } } }
            ]
}  
headers = {    "Content-Type": "application/json",
    "Authorization": "Bearer " + token,
    "Accept": "application/json, application/vnd.sas.report.operations.results+json, application/vnd.sas.report.operations.error+json, application/vnd.sas.error+json"

} response = requests.post(url, json=payload, headers=headers) print(response.json())   endsubmit; run;  

 

When you execute the code, the response is printed;

 

{'resultReportId': 'ce648f31-8cb5-4e8c-9666-57dbff5689ba', 'resultReportName': 'Report123',
'resultReportUri':'/reports/reports/ce648f31-8cb5-4e8c-9666-57dbff5689ba', 
'resultFolderUri':'/folders/folders/@myFolder', 
'operations': [{'name':'ds7', 'label': 'CLASS', 'status': 'Success'}], 'status': 'Success', 'links': [{'method': 'GET',
'rel': 'getReport', 'href': '/reports/reports/ce648f31-8cb5-4e8c-9666-57dbff5689ba', 'uri': '/reports/reports/ce648f31-8cb5-4e8c-9666-57dbff5689ba', 'type': 'application/vnd.sas.report+json'}]}

 

The expected response is also documented:

 

42818_3_8_Response_Sample.png

 

In a section further below, we will need to extract a specific element from the response. To achieve this, we need to understand the structure of the response, which is what the Response Sample in the documentation is for. This response sample is for response code 201, which indicates a successful API call. These codes are standardized, and the most common responses for each endpoint are documented:

 

42818_3_9_Response_Codes.png

 

The response contains the report ID of the created report. We will need this report ID to update the report. By understanding the structure of the response, we can figure out how to extract it. In this case, it is not nested and we can directly extract as follows:

 

proc python;
submit;

reportID = response.json()['resultReportId']
print("Report ID: " + reportID)

endsubmit;
run;

 

What are ETags

 

Before we move on to updating the report, we need to understand what ETags are. When updating content, SAS Viya has a built-in safety mechanism to prevent unwanted overwrites of a resource. Specifically, to prevent so called mid-air collisions: two people writing different changes to the same resource at the same time. This is done with the ETag (entity tag).

 

The ETag is a HTTP standard and identifies a specific version of a resource. Therefore, each time we want to update a report, we need to include its most recent ETag in the request. We can retrieve the most recent ETag using the retrieve_etag function, which relies on the getReport endpoint in the Reports REST API (mentioned earlier):

 

proc python;
submit;

def retrieve_etag(reportID):
    reportURI = 'reports/reports/' + reportID
    url = str(host) + '/' + reportURI
    print(url)
    headers = {"Authorization": "Bearer " + token,
               "Accept" : "application/json"}
    response = requests.get(url, headers=headers)
    return response.headers['ETag']

endsubmit;
run;

 

We put this into a Python function so that we can easily call it every time we want to update the report. The function just requires the report ID. You will need to rerun that block of code to redefine the function when the session expires.

 

Modifying data item properties in an existing report

 

Using the ETag, we can now update the report. We need to include the ETag in the request header, with the If-Match key.

 

We can apply various operations using the updateReport endpoint. Recall you can include multiple operations in a request.

 

Operation Description
addDataOperationRequest Operation to add a data source to a report. Allows you to filter by data items and indicate properties for each data item (name, format, aggregation, classification and geographic context).
updateDataOperationRequest Operation to update an existing data source in a report. Allows you to change data item properties (name, format, aggregation, classification and geographic context).
changeDataOperationRequest Operation to replace a data source used in a report with another. This includes a facility for data item mapping if data items in replacement data source have different names.
applyDataViewOperationRequest Operation to apply a data view to a report.
addPageOperationRequest Operation to add a page to a report.
addObjectOperationRequest Operation to add an object to a report.
updateObjectOperationRequest Operation to update an object on a report.
setParameterValueOperationRequest Operation to set a parameter value on a report.

 

In this section, we change the aggregation of a data item from sum to average. Note that as of Stable 2025.04, you always need to specify the classification of the data item (in this case: measure), even if it doesn’t change.

 

proc python;
submit;

ETag = retrieve_etag(reportID)
url = str(host) + "/visualAnalytics/reports/" + reportID
payload = {
    "version": 1,
    "resultFolder": "/folders/folders/@myFolder",
    "resultReportName": "Report123",
    "resultNameConflict": "replace",
    "operations": [{ "updateData": {
                "data": { "name": "CLASS" },
                "dataItems": [
                    {
                        "dataItem": "Height",
                        "properties": {
                            "aggregation": "average",
                            "classification" : "measure"
                        }
                    }
                ]
            } }]
}
headers = {
    "If-Match": ETag,
    "Content-Type": "application/json",
    "Authorization": "Bearer " + token,
    "Accept": "application/json, application/vnd.sas.report.operations.results+json, application/vnd.sas.report.operations.error+json, application/vnd.sas.error+json"
}

response = requests.put(url, json=payload, headers=headers)
print(response.json())

endsubmit;
run;

 

Checking if an object already exists

 

Before adding an object, we will check whether that object is already in the report. If you run the addObject operation in the updateReport API endpoint, it doesn’t check whether the object already exists. To avoid having the same bar chart twice, we can use the Get report content elements endpoint in the Reports API. It returns the contents of the report, including all objects. We can check whether an object with a specific name (in our case Bar – Sex 1) is already in the report:

 

proc python;
submit;

url = str(host) + "/reports/reports/" + reportID + "/content/elements"
headers = {
    "Accept-Item": "application/vnd.sas.report.content.element+json",
    "Authorization": "Bearer " + token,
    "Accept": "application/json, application/vnd.sas.collection+json, application/vnd.sas.error+json"
}

response = requests.get(url, headers=headers)
if "Bar - Sex 1" in response.text:
    print("The object 'Bar - Sex 1' already exists. We don't need to add it.")
else:
    print("The object 'Bar - Sex 1' does not exist. Execute the next cell to add it.")

endsubmit;
run;

 

Note we are simply parsing the report content for the object name. This response works as long as the object name is not the same as something else in the report definition. In this case, we just print out whether the object already exists or not. Ideally, you would add the code from the following section into the else block of the code.

 

Adding objects to an existing report

 

Finally, we can add objects to the report. Note the updateReport endpoint accepts either a reportObject or an object key in the request body. The reportObject key is meant to add an existing object from another report. Use the object key to add a completely new object. We can decide on how to position each object:

 

Position Description
reportPlacement Allows placement in the start or the end of canvas or the header of a new page.
pagePlacement Allows placement in the start or the end of canvas or the header of an existing page.
containerPlacement Allows placement at the start or the end of an existing container.
relativeToObjectPlacement Allows placement before, after, to the left, right, on top, or on the bottom of an existing object.

 

In the following code, we add a bar chart and a text object.

 

proc python;
submit;
import requests
ETag = retrieve_etag(reportID)
url = str(host) + "/visualAnalytics/reports/" + reportID
payload = {
    "version": 1,
    "resultFolder": "/folders/folders/@myFolder",
    "resultReportName": "Report123",
    "resultNameConflict": "replace",
    "operations": [
        {
            "operationId": "barChart",
            "includeObjectInResponse": True,
            "addObject": {
                "object": {"barChart": {
                        "dataSource": "CLASS",
                        "dataRoles": {
                            "category": "Sex",
                            "measures": ["Frequency"]
                        }
                    } },
                "placement": { "page": {
                        "target": "Page 1",
                        "position": "start"
                    } }
            }
        },
        {
            "operationId": "text",
            "includeObjectInResponse": True,
            "addObject": {
                "object": {"text": {
                        "options": {
                            "content": "This bar chart shows the frequency of each Sex."
                        }
                    } },
                "placement": {
                    "relativeToObject": {
                        "target": "Bar - Sex 1",
                        "position": "top"
                        }
                    } }
            }
    ]
}

headers = {
    "If-Match": ETag,
    "Content-Type": "application/json",
    "Authorization": "Bearer " + token,
    "Accept": "application/json, application/vnd.sas.report.operations.results+json, application/vnd.sas.report.operations.error+json, application/vnd.sas.error+json"
}

response = requests.put(url, json=payload, headers=headers)
print("Response Code: " + str(response.status_code))
print(response.json())

endsubmit;
run;

 

Conclusion

 

As you can see, most requests follow a similar structure. Once you understand how to navigate the documentation, you can make the necessary adjustments to the code. There is even more you can do with REST API. This will be covered in future posts.

 

Resources

 

 

 

Find more articles from SAS Global Enablement and Learning here.

Version history
Last update:
4 weeks ago
Updated by:
Contributors

hackathon24-white-horiz.png

The 2025 SAS Hackathon Kicks Off on June 11!

Watch the live Hackathon Kickoff to get all the essential information about the SAS Hackathon—including how to join, how to participate, and expert tips for success.

YouTube LinkedIn

SAS AI and Machine Learning Courses

The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.

Get started

Article Tags