Documentation > Best Practices and Examples > AccessAPI

Implementing the AccessAPI in Crownpeak

You can use the AccessAPI to synchronize assets from a local folder structure to your instance. The AccessAPI uses C#, Javascript (js), and Powershell. Developers can now create, import, and manage CMS Assets, Configurations, and Workflows through a set of public API calls protect by access controls and rate limiting governors.

Overview


API keys are rate limited at a speed of 9 requests every 3 seconds, it is a good idea to keep track of the number of requests you send at one time and watch for a response code of 429 if you go over the limit. Look to the code samples for specific ways to handle this condition. This rate limit may change. Also, we do have the ability to modify this rate level per API key.

These examples are intended to help developers adjust to using the AccessAPI REST interface. These examples focus on server-side usage (C# example, Javascript example, Powershell example). Browser-based usage examples will be added in the coming months.

For more information about the API key and authentication methods please read Tips for Using the CrownPeak AccessAPI.

C# Example


The helper libraries for the C# example and for the C# PCL are available for download on Connect.

namespace SyncFolder
{
    class Program
    {
        private static Dictionary<string, int> folderCache = new Dictionary<string, int>(); //folder id cache, so we don't have excessive asset path lookups
        private static AccessAsset accessAsset; //for accessability, used in multiple methods
        
        static void Main(string[] args)
        {
            try
            {
                #region handle arguments and validation
                if (args.Length < 2)
                {
                    Console.WriteLine("Usage: syncFolder [local folder to synchronize] [destination CMS folder]");
                }
                string localFolder = args[0];
                string destFolder = args[1];
                if (!Directory.Exists(localFolder))
                {
                    Console.WriteLine("Could not find local folder: {0}", localFolder);
                    return;
                }
                #endregion
                #region get password
                string username = ConfigurationSettings.AppSettings["username"];
                Console.Write("Enter pw for {0}: ", username);
                string pw = getSecuredString();
                Console.WriteLine("");
                Console.WriteLine("Authenticating: {0} ", username);
                #endregion
                Console.WriteLine("Communicating with {0}/{1}", ConfigurationSettings.AppSettings["host"], ConfigurationSettings.AppSettings["instance"]);
                var session = new AccessSession(
                    ConfigurationSettings.AppSettings["host"],
                    ConfigurationSettings.AppSettings["instance"],
                    username,
                    pw,
                    ConfigurationSettings.AppSettings["apiKey"]);
                accessAsset = new AccessAsset(session.Client);
                processFolder(localFolder.ToLower(), destFolder);
                var accessAuth = new AccessAuth(session.Client);
                accessAuth.Logout();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: {0}", ex.Message);
            } 
            Console.Write("Press, almost, any key to continue...");
            Console.ReadLine();
        }
        //get a list of all files in a folder, including files in subfolders, and process them
        private static void processFolder(string localFolder, string cmsDestinationPath)
        {
            var files = Directory.GetFiles(localFolder, "*.*", SearchOption.AllDirectories);
            if (files != null && files.Length > 0)
            {
                foreach (var file in files)
                {
                    CheckOrUploadFile(localFolder, cmsDestinationPath, accessAsset, file);
                }
            }
        }
        //checks if a file already exists, uploads it if not
        private static void CheckOrUploadFile(string localFolder, string cmsDestinationPath, AccessAsset accessAsset, string file)
        {
            string cmsPathToCheck = "";
            //1 - check if file already exists
            cmsPathToCheck = file.ToLower().Replace(localFolder, cmsDestinationPath).Replace("\\", "/");
            var existsResponse = accessAsset.Exists(new AssetExistsRequest(cmsPathToCheck));
            if (existsResponse.exists)
            {
                Console.WriteLine("Skipping, asset {0} already exists.  (id: {1})", cmsPathToCheck, existsResponse.assetId);
            }
            else
            {
                //file was not found in the CMS instance, so we will upload it, but first make sure we do have an existing folder structure in the CMS
                //2 - check if folder exists, create otherwise 
                string path = Path.GetDirectoryName(file);
                int folderId = -1;
                if (folderCache.ContainsKey(path))
                {
                    folderId = folderCache[path];
                }
                else
                {
                    //attempt to create folder and cache new assetId as the folderId
                    cmsPathToCheck = path.ToLower().Replace(localFolder, cmsDestinationPath).Replace("\\", "/");
                    existsResponse = accessAsset.Exists(new AssetExistsRequest(cmsPathToCheck));
                    if (existsResponse.exists)
                    {
                        folderId = existsResponse.assetId;
                        folderCache.Add(path, folderId);
                    }
                    else
                    {
                        //could not find folder, split and build path
                        folderId = BuildFolderTree(cmsPathToCheck);
                        if (folderId < 0)
                        {
                            //something went wrong, exit app, so we don't upload files to wrong folders, or have excessive # of errors.
                            Console.WriteLine("Error, Unable to create folder {0} ", cmsPathToCheck); 
                            Environment.Exit(-1);
                        }
                        folderCache.Add(path, folderId); 
                    }
                }
                //upload asset here
                AssetUploadRequest req = new AssetUploadRequest(Path.GetFileName(file), folderId);
                req.bytes = File.ReadAllBytes(file);
                var resp = accessAsset.Upload(req);
                if (resp.IsSuccessful)
                {
                    Console.WriteLine("upload successful: {0} in {1}", file, path, resp.asset.id);
                }
                else
                {
                    Console.WriteLine("Error uploading file: {0} in {1}: {2}", file, path, resp.ErrorMessage);
                }
            }
        }
        #region helper functions
        //found a missing folder, recreate from all parts, since we don't know what 
        private static int BuildFolderTree(string cmsPathToCheck)
        {
            int id = -1;
            string[] parts = cmsPathToCheck.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
            string pathToCheck = "/";
            foreach (var part in parts)
            {
                pathToCheck += part + "/"; 
                var existsResponse = accessAsset.Exists(new AssetExistsRequest(pathToCheck));
                if (existsResponse.exists)
                {
                    Console.WriteLine("base folder exists {0}", pathToCheck);
                    id = existsResponse.assetId;
                }
                else
                { 
                    Console.WriteLine("need to create {0}", pathToCheck);
                    //var resp = accessAsset.Create(new AssetCreateRequest(part, id, AssetType.Folder));
                    var resp = accessAsset.Create(part, id, AssetType.Folder);
                    if (resp.IsSuccessful)
                    {
                        id = resp.asset.id;
                    }
                    else
                    {
                        //create, assign tid, if failed, set to -1
                        id = -1;
                        break;
                    }
                } 
            }
            return id;
        }
        //based on http://stackoverflow.com/questions/3404421/password-masking-console-application
        private static string getSecuredString()
        {
            ConsoleKeyInfo key;
            string pw = "";
            do
            {
                key = Console.ReadKey(true);
                if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
                {
                    pw += key.KeyChar;
                    Console.Write("*");
                }
                else
                {
                    if (key.Key == ConsoleKey.Backspace && pw.Length > 0)
                    {
                        pw = pw.Substring(0, (pw.Length - 1));
                        Console.Write("\b \b");
                    }
                }
            }
            // Stops Receving Keys Once Enter is Pressed
            while (key.Key != ConsoleKey.Enter);
            return pw;
        }
        #endregion
    }
}

JS Example


This is a basic example of using the accessAPIModule to interact with the Crownpeak CMS AccessAPI.

This is a basic example of using the accessAPIModule to interact with the Crownpeak CMS AccessAPI. The helper libraries for Node.js are available for download on Connect.

var requestify = require('requestify');
var prompt = require('sync-prompt').prompt;
var colors = require('colors');
var fs = require('fs');
var apikey = "";
var domain = '';
var instance = '';
var cookies = null;
var username = '';
var password = '';
//read a json file with configuration fields, so we don't have to hard code them
if (fs.existsSync('config.json')) {
    console.log('reading config.json'.yellow.bold);
    var config = JSON.parse(fs.readFileSync('config.json', { "encoding": "utf8" }));
    instance = config.instance;
    domain = config.server;
    apikey = config.accessKey;
    username = config.username;
    console.log('instance: ' + config.instance);
    console.log('domain: ' + config.server);
    console.log('username: ' + config.username);
}
var hidden = true;
//prompt user for password
if (password.length == 0) password = prompt('Password: ', hidden);
var baseURL = domain + '/' + instance + '/cpt_webservice/accessapi';
//setup http headers
var options = {
    body: '',
    method: 'POST',
    headers: {
        'x-api-key': apikey,
        'Content-Type': 'application/json; charset=utf8',
        //'Accept-Encoding': 'gzip, deflate '
    },
};
exports.auth = function (callback) {
    var body = { "instance": instance, "username": username, "password": password, "remember_me": false, "timeZoneOffsetMinutes": -480 };
    return restPost('/auth/authenticate', body, callback);
}
exports.logout = function (callback) { 
    return restPost('/auth/logout', null, callback);
}
exports.AssetExists = function (path, callback) {
    var body = { "assetIdOrPath": path };
    return restPost('/asset/Exists', body, callback);
}
exports.AssetUpload = function (newName, folderId, modelId, workflowId, bytes, callback) {
    var body = { "newName": newName,
    "destinationFolderId": folderId,
    "modelId": modelId,
    "workflowId": workflowId,
    "bytes": bytes };
    if (folderId == 0 || folderId == undefined) return callback('not allowed to import to root'); 
    return restPost('/asset/Upload', body, callback);
}
exports.AssetCreate = function (newName, folderId, modelId, type, devTemplateLanguage, templateId, workflowId, callback) {
    var body = { "newName": newName,
    "destinationFolderId": folderId,
    "modelId": modelId,
    "type": type,
    "devTemplateLanguage": devTemplateLanguage,
    "templateId": templateId,
    "workflowId": workflowId } 
    if (folderId == 0 || folderId == undefined)
    {
        console.log('create asset error, folderId = ' + folderId);
        return callback('not allowed to import to root'); 
    } 
    return restPost('/asset/Create', body, callback);
}
//main http call
function restPost(url, body, callback) {
    url = baseURL + url;
    console.log("calling: ".yellow.bold + url.green.bold);
    options.body = body;
    //check if we need pass the cookies
    if (cookies != null) {
        // todo: try to reuse headers from above.
        options.headers = {
            'x-api-key': apikey,
            'Content-Type': 'application/json; charset=utf8',
            'Cookie': cookies
            //'Accept-Encoding': 'gzip, deflate '
    }
}
requestify.request(url, options).then(function (resp) {
        processCookies(resp);
        callback(JSON.parse(resp.body));
    }, function (err) {
        //todo: handle http 429, rate limiting busy, retry-after
        callback(JSON.parse(err.body));
    });
}
//handles cookies between http calls
function processCookies(resp, callback) {
    if (resp.headers['set-cookie'] != null) {
        var cooks = resp.headers['set-cookie'];
        var newCookies = '';
        var cookieCount = 0;
        cooks.forEach(function (cookie) {
            var parts = cookie.split(';');
            //console.log(parts);
            if (cookieCount++ > 0) {
                newCookies += "; ";
            }
            newCookies += parts[0];
        });
        cookies = newCookies;
    }
}

Powershell Example


The helper libraries for Powershell are available for download on Connect.

    ###################################################################################################
    #
    # Summary: AccessAPI service library
    #
    ###################################################################################################
    $Global:CP_AAPI_server = 'https://wcd.crownpeak.com'
    $Global:CP_AAPI_instance = 'DevSandbox'
    $Global:CP_AAPI_cc = $null
    $Global:CP_AAPI_accessKey = ''
    $Global:CP_AAPI_skey = ''
    $Global:CP_AAPI_username = ''
    #Set-Variable conFileType -Value 2 -option Constant
    #Set-Variable conFolderType -Value 4 -option Constant
    $conFolderType = 4
    $conFileType = 2
    $recursivePostCount = 0
    $debug = $false
    #builds the AccessAPI header
    function Get-CMS_Signature([string]$absolutePath, [string]$body, [string]$method='POST') { 
        $headers = @@{ 'x-api-key' = $($Global:CP_AAPI_accessKey)} 
        return $headers
    }
    #builds the AccessAPI header with the secret key (for server side calls only, do not use the secret key on the client)
    function Get-CMS_SHA1_Signature([string]$absolutePath, [string]$body, [string]$method='POST') { 
        $datetime = (Get-Date).ToUniversalTime().ToString( "yyyy-MM-ddTHH:mm:ss.fffffffZ" )
        $data = "$method`n$absolutePath`n$body`n$datetime"
        $hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
        $hmacsha1.Key = [System.Text.Encoding]::UTF8.GetBytes($Global:CP_AAPI_skey);
        
        $signature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($data))); 
        $headers = @@{ 'Authorization' = "CP $($Global:CP_AAPI_accessKey):$signature"; 'cp-datetime'="$datetime"; 'Accept-Encoding'= 'gzip, deflate'} 
        return $headers
    }
    #make http call to the CMS, handle HTTP 429 - rate limit reached, retry-after
    function Invoke-CMS_POST([string]$path, [string]$body) {
        if ($recursivePostCount++ -gt 10) {
            Exit -1  #recursive limit reached
        }
        $httpVerb = 'POST'
        $absolutePath= "/$Global:CP_AAPI_instance/cpt_webservice/accessapi$path"
        $headers = Get-CMS_Signature $absolutePath $body $httpVerb
        $uri = "$Global:CP_AAPI_server$absolutePath"
        #auth must be called first, since it will setup the cookie, otherwise this call will fail due to 'access denied'
        #$resp = Invoke-RestMethod $uri -Method $httpVerb -WebSession $Global:CP_AAPI_cc -Headers $headers -Body $body -ContentType 'application/json' -ErrorVariable errorVar -ErrorAction SilentlyContinue
        $webresp = Invoke-WebRequest $uri -Method $httpVerb -WebSession $Global:CP_AAPI_cc -Headers $headers -Body $body -ContentType 'application/json' -ErrorVariable errorVar -ErrorAction SilentlyContinue 
        if($webresp -eq $null)
        {
            $webException = $($errorVar[0].GetBaseException() -as [System.Net.WebException]) 
            if($webException -ne $null -and $($webException.Response.StatusCode -as [int]) -eq 429)#no System.Net.HttpStatusCode enumeration for 429
            {
                $sleepTime = 1 #fallback value, if Retry-After header is not there 
                $retryHeader = $webException.Response.Headers["Retry-After"] 
                if($retryHeader -ne $null -and $($retryHeader -as [int]) -ne $null)
                {
                    $sleepTime = $retryHeader -as [int]
                }
                if ($debug) { write-host "429 received, retrying in $sleepTime seconds... ($recursivePostCount) " -ForegroundColor Cyan }
                Start-Sleep -s $sleepTime
                return Invoke-CMS_POST $path $body 
            }
            else
            {
                exit 1
            }
        }
        else
        {
            #success
            if ($debug -and $webresp.Headers.ContainsKey('cp-timetaken')) {
                write-host 'timetaken: ' $webresp.Headers.Item('cp-timetaken')
            }
            $resp = $webresp.Content | ConvertFrom-Json
            $recursivePostCount = 0
        }
        return $resp 
    }
    #helper function to make authentication call
    function Invoke-CMS_Auth([string]$body) {
        $absolutePath= "/$Global:CP_AAPI_instance/cpt_webservice/accessapi/auth/authenticate" 
        $headers = Get-CMS_Signature $absolutePath $body 'POST'
        $uri = "$Global:CP_AAPI_server$absolutePath"
        $resp = Invoke-RestMethod $uri -Method 'POST' -SessionVariable Global:CP_AAPI_cc -Headers $headers -Body $body -ContentType 'application/json' -ErrorVariable err -ErrorAction SilentlyContinue
        if ($err -ne $null)
        {
            $err
        }
        return $resp
    }
    #main authentication call
    function Invoke-CMS_Authenticate($cred) {
        $timeZoneOffset = ([TimeZoneInfo]::Local).BaseUtcOffset.TotalMinutes
        $body = "{`"instance`":`"$Global:CP_AAPI_instance`",`"username`":`"$($cred.UserName)`",`"password`":`"$($cred.GetNetworkCredential().Password)`",`"remember_me`":false,`"timeZoneOffsetMinutes`":$timeZoneOffset}"
        return Invoke-CMS_Auth $body
    }
    #logout call
    function Invoke-CMS_Logout() {
        $body = ""
        return  Invoke-CMS_Post '/auth/logout' $body
    }
    #check if an asset exists in the CMS
    function Invoke-CMS_AssetExists($assetPath) {
        $body = "{`"assetIdOrPath`":`"$assetPath`"}"
        return  Invoke-CMS_Post '/Asset/Exists' $body
    }
    #create an asset at the provided folderId
    function Invoke-CMS_CreateAsset([string]$newName, [int]$folderId, [int]$modelId, [int]$type, [int]$devTemplateLanguage = -1, [int]$templateId = -1, [int]$workflowId = -1) {
        $req = @@{}
        $req.Add('newName', "`"$newName`"")
        $req.Add('destinationFolderId', $folderId)
        $req.Add('modelId', $modelId)
        $req.Add('type', $type)
        $req.Add('devTemplateLanguage', $devTemplateLanguage) 
        $req.Add('templateId', $templateId)
        $req.Add('workflowId', $workflowId) 
        $body = $req | ConvertTo-Json
        
        return Invoke-CMS_Post '/Asset/Create' $body
    }
    #upload a binary asset
    function Invoke-CMS_UploadAsset([string]$newName, [int]$folderId, [int]$modelId, [int]$workflowId, [string]$Base64EncodedBytes) {
        $req = @@{}
        $req.Add('newName', "`"$newName`"")
        $req.Add('destinationFolderId', $folderId)
        $req.Add('modelId', $modelId)
        $req.Add('workflowId', $workflowId) 
        $req.Add('bytes', $Base64EncodedBytes) 
        $body = $req | ConvertTo-Json
        return Invoke-CMS_Post '/Asset/Upload' $body
    }
    #get cms assets
    function Invoke-CMS_GetPagedAssets( [int]$assetId,
        [int]$page = 0, 
        [int]$pageSize=10, 
        [string]$sortColumn='label', 
        [string]$orderType = 'Ascending', 
        [string]$visibilityType = 'Normal') {
        $body = "{`"assetId`":$assetId,`"currentPage`":$page,`"pageSize`":$pageSize,`"sortColumn`":`"$sortColumn`",`"orderType`":`"$orderType`", `"visibilityType`":`"$visibilityType`", `"ignoreFilter`":true, `"ignoreSort`":true}"
        return Invoke-CMS_Post '/Asset/Paged' $body
    }
    #set configuration items for use by the script
    function Set-CMS_Config($configFileName) {
        #todo: add error handling
        $config = gc config.json  | ConvertFrom-Json
        $Global:CP_AAPI_server = $config.server
        $Global:CP_AAPI_instance = $config.instance
        $Global:CP_AAPI_accessKey = $config.accessKey
        $Global:CP_AAPI_username = $config.username
    }
    #alternative method to read password for use in the script
    function Get-CMS_Credentials {
        if ($global:CP_AAPI_cred -eq $null)
        {
            $global:CP_AAPI_cred = Get-Credential -Message 'Please enter CMS credentials' -UserName $Global:CP_AAPI_username
        } 
        return $global:CP_AAPI_cred
    }

Connect with CrownPeak