SugarCRM SupportDocumentationSugar DeveloperSugar Developer Guide 9.0IntegrationWeb ServicesREST APIExtending Endpoints

Extending Endpoints

Overview

How to add your own custom endpoints to the REST API.

Custom Endpoints

With the REST architecture, you can easily define your own endpoint. All custom global endpoints will be located in ./custom/clients/<client>/api/. All custom endpoints specific to a module will be located in ./custom/modules/<module>/clients/<client>/api/.

Note: The Sugar application client type is "base". More information on the various client types can be found in the Clients section.

Defining New Endpoints

The endpoint class will be created in ./custom/clients/<client>/api/ and will extend SugarApi. Within this class, you will need to implement a registerApiRest() function that will define your endpoint paths. When creating a new endpoint, please note that the file name must match the class name for the endpoint definitions. The example below demonstrates creating a GET endpoint.

./custom/clients/base/api/MyEndpointsApi.php

<?php

if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

class MyEndpointsApi extends SugarApi
{
    public function registerApiRest()
    {
        return array(
            //GET
            'MyGetEndpoint' => array(
                //request type
                'reqType' => 'GET',

                //set authentication
                'noLoginRequired' => false,

                //endpoint path
                'path' => array('MyEndpoint', 'GetExample', '?'),

                //endpoint variables
                'pathVars' => array('', '', 'data'),

                //method to call
                'method' => 'MyGetMethod',

                //short help string to be displayed in the help documentation
                'shortHelp' => 'An example of a GET endpoint',

                //long help to be displayed in the help documentation
                'longHelp' => 'custom/clients/base/api/help/MyEndPoint_MyGetEndPoint_help.html',
            ),
        );
    }

    /**
     * Method to be used for my MyEndpoint/GetExample endpoint
     */
    public function MyGetMethod($api, $args)
    {
        //custom logic
        return $args;
    }

}

?>

Note: The file name must match the class name for the endpoint definitions. Given the example above, a class name of MyEndpointsApi must be created as MyEndpointsApi.php.

registerApiRest() Method

Your extended class will contain two methods. The first method is registerApiRest() which will return an array that defines your REST endpoints. Each endpoint will need the following properties:

Name Type Description
reqType Array | String An array of one or many request types of the endpoint. Possible values are: GET, PUT, POST and DELETE.
noLoginRequired Boolean Determines whether the endpoint is authenticated. Setting this value to true will remove the authentication requirement.
path Array

The endpoint path. An endpoint path of:

'path' => array('MyEndpoint', 'GetExample'),

Will equate to the URL path of http://{site url}/rest/{REST VERSION}/MyEndpoint/GetExample.

To pass in a variable string to your endpoint, you will add a path of '?'. This will allow you to pass URL data to your selected method given that it is specified in the pathVars.
pathVars Array

The path variables. For each path on your endpoint, you can opt to pass its value in as a parameter to your selected method. An empty path variable will ignore the path.

Example:

'path' => array('MyEndpoint', 'GetExample', '?'),
'pathVars' => array('', '', 'data'),

The above will pass information to your selected method as the $args parameter when calling http://{site url}/rest/{REST VERSION}/MyEndpoint/GetExample/MyData

method String The method to pass the pathVars to. This can be named anything you choose and will be located in your SugarApi extended class.
shortHelp String A short help string for developers when looking at the help documentation.
longHelp String Path to a long help file. This file will be loaded when a user clicks an endpoint from the help documentation.
minVersion Integer The minimum API Version this endpoint can be used with. See Scoring Section below. 
maxVersion Integer  The maximum API Version this endpoint can be used with. See Scoring Section below. 
extraScore Integer  Add an extra score value to the Scoring of this endpoint, to place priority on its usage. See Scoring Section below.

Endpoint Method

The second method can be named anything you choose but it is important to note that it will need to match the name of the method you specified for your endpoint. This method will require the parameters $api and $args. The MyGetMethod() in the example above will be used when the endpoint MyEndpoint/GetExample is called. Any path variables, query string parameters or posted data will be available in the $args parameter for you to work with.

public function MyEndpointMethod($api, $args)
{
    //logic
    return $returnData;
}

Help Documentation

The final step once you have your endpoint working is to document it. This file can reside anywhere in the system and will be referenced in your endpoints longHelp property. An example of the help documentation can be found below:

custom/clients/base/api/help/MyEndPoint_MyGetEndPoint_help.html

<h2>Overview</h2>
<span class="lead">
    A custom GET endpoint. Returns the $args parameter that is passed to the endpoint method.
</span>

<h2>Path Variables</h2>
<table class="table table-hover">
    <thead>
    <tr>
        <th>Name</th>
        <th>Description</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            :data
        </td>
        <td>
            Data string to pass to the endpoint.
        </td>
    </tr>
    </tbody>
</table>

<h2>Input Parameters</h2>
<span class="lead">
    This endpoint does not accept any input parameters.
</span>

<h2>Result</h2>
<table class="table table-hover">
    <thead>
    <tr>
        <th>Name</th>
        <th>Type</th>
        <th>Description</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            __sugar_url
        </td>
        <td>
            String
        </td>
        <td>
            The endpoint path.
        </td>
    </tr>
    <tr>
        <td>
            data
        </td>
        <td>
            String
        </td>
        <td>
            The data from path variable.
        </td>
    </tr>
    </tbody>
</table>

<h3>Output Example</h3>
<pre class="pre-scrollable">
{
    "__sugar_url":"v10\/MyEndpoint\/GetExample\/MyData",
    "data":"MyData"
}
</pre>

<h2>Change Log</h2>
<table class="table table-hover">
    <thead>
    <tr>
        <th>Version</th>
        <th>Change</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            v10
        </td>
        <td>
            Added <code>/MyEndpoint/GetExample/:data</code> GET endpoint.
        </td>
    </tr>
    </tbody>
</table>

Quick Repair and Rebuild

Once all of the files are in place, you will need to navigate to Admin > Repair > Quick Repair and Rebuild. This will rebuild the ./cache/file_map.php and ./cache/include/api/ServiceDictionary.rest.php files to make your endpoint available.

Example

./custom/clients/base/api/MyEndpointsApi.php

<?php

if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

class MyEndpointsApi extends SugarApi
{
    public function registerApiRest()
    {
        return array(
            //GET & POST
            'MyGetEndpoint' => array(
                //request type
                'reqType' => array('GET','POST'),

                //set authentication
                'noLoginRequired' => false,

                //endpoint path
                'path' => array('MyEndpoint', 'GetExample', '?'),

                //endpoint variables
                'pathVars' => array('', '', 'data'),

                //method to call
                'method' => 'MyGetMethod',

                //short help string to be displayed in the help documentation
                'shortHelp' => 'An example of a GET endpoint',

                //long help to be displayed in the help documentation
                'longHelp' => 'custom/clients/base/api/help/MyEndPoint_MyGetEndPoint_help.html',
            ),
        );
    }

    /**
     * Method to be used for my MyEndpoint/GetExample endpoint
     */
    public function MyGetMethod($api, $args)
    {
        //custom logic
        return $args;
    }

}

?>

Redefining Existing Endpoints

With endpoints, you may have a need to extend an existing endpoint to meet your needs. When doing this it is important that you do not remove anything from the return array of the endpoint. Doing so could result in unexpected behavior due to other functionality using the same endpoint.

For this example, we will extend the ping endpoint to return "Pong <timestamp>". To do this, we will need to extend the existing PingApi class and define our method overrides. When creating a new endpoint, please note that the file name must match the class name for the endpoint definitions. For our example, we will prefix the original class name with "custom" to create our overriding endpoint.

./custom/clients/base/api/CustomPingApi.php

<?php

if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require_once("clients/base/api/PingApi.php");

class CustomPingApi extends PingApi
{
    public function registerApiRest()
    {
        //in case we want to add additional endpoints
        return parent::registerApiRest();
    }

    //override to modify the ping function of the ping endpoint
    public function ping($api, $args)
    {
        $result = parent::ping($api, $args);

        //append the current timestamp
        return $result . ' ' . time();
    }
}

Note: The file name must match the class name for the endpoint definitions. Given the example above, a class name of CustomPingApi must be created as CustomPingApi.php.

As you can see, we extended registerApiRest to fetch existing endpoint definitions in case we want to add our own. We then added an override for the ping method to return our new value. Once the file is in place you will need to navigate to Admin > Repair > Quick Repair and Rebuild. Once completed, any call made to <url>/rest/{REST VERSION}/ping will result in a response of "ping <timestamp>".

Endpoint Scoring

When generating custom endpoints or overriding existing endpoints, the system can determine which endpoint to use based on the score placed on the registered endpoints. The scoring calculation works in the following manner for registered endpoints.

Route Path Score
? 0.75
<module> 1.0
Exact Match 1.75
Custom 0.5
Endpoint 'extraScore' property Defined Extra Score

 

The defined Path and extraScore properties in the registerApiRest() method, help determine the score for any given Endpoint. The higher the score allows the API to determine which Endpoint is being used for a given request. For example, if you had extended the /Accounts/:record GET API Endpoint as follows: 

/custom/modules/Accounts/clients/base/api/CustomAccountsApi.php

<?php

if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require_once("clients/base/api/ModuleApi.php");

class CustomAccountsApi extends ModuleApi
{
    public function registerApiRest()
    {
        return array(
            //GET & POST
            'getAccount' => array(
                //request type
                'reqType' => array('GET'),

                //set authentication
                'noLoginRequired' => false,

                //endpoint path
                'path' => array('Accounts', '?'),

                //endpoint variables
                'pathVars' => array('module', 'record'),

                //method to call
                'method' => 'retrieveRecord',

                //short help string to be displayed in the help documentation
                'shortHelp' => 'An example of a GET endpoint',

                //long help to be displayed in the help documentation
                'longHelp' => 'custom/clients/base/api/help/MyEndPoint_MyGetEndPoint_help.html',
            ),
        );
    }

    /**
     * Method to be used for my Accounts/:record endpoint
     */
    public function retrieveRecord($api, $args)
    {
        //custom logic
        return parent::retrieveRecord($api,$args);
    }

}

?>

 This Endpoint will be used when accessing /Accounts/<record_id> since the Exact Match of Accounts in the path property gives a greater score than the <module> match that comes with the standard /<module>/:record Endpoint defined in ModuleApi.

Endpoint Versioning

Part of the scoring calculation allows for versioning of the API Endpoints to accommodate the same endpoint path in the Rest API, for other versions of the API. If you have a custom Endpoint at /rest/v10/CustomEndpoint, and would like to alter the logic slightly for another use without deprecating or removing the old Endpoint, you can use the versioning techniques to have the new logic reside at /rest/v11/CustomEndpoint. The following table describes how the Versioning properties on the Endpoint definition affect the score of the Endpoint to allow the API to determine which Endpoint should be used for a given request.

 Property Score 
 minVersion 0.02 + minVersion/1000 
 maxVersion 0.02 
 minVersion === maxVersion (i.e. Both minVersion and maxVersion properties are defined on Endpoint to the same value) 0.06 + minVersion/1000

 

To allow for additional Versions in the API, you will first need to customize the 'maxVersion' property in the $apiSettings array. To do this, create ./custom/include/api/metadata.php. Inside the file you can set the $apiSettings['maxVersion'] property as follows:

<?php

$apiSettings['maxVersion'] = 11;

 

Once you have configured the Max Version property, you can then set the minVersion and maxVersion properties on the Endpoint definition in the custom registerApiRest() method. For example, the following Endpoint definitions: 

<?php 
...
    return array(
        'foo1' => array(
            'reqType' => 'GET',
            'path' => array('Accounts','config',
            'pathVars' => array('module', 'record'),
            'method' => 'foo1',
        ),
        'foo2' => array(
            'reqType' => 'GET',
            'minVersion' => 11,
            'path' => array('Accounts','config',
            'pathVars' => array('module', 'record'),
            'method' => 'foo2',
        ),
        'foo3' => array(
            'reqType' => 'GET',
            'minVersion' => 11,
            'maxVersion' => 11,
            'path' => array('Accounts','config',
            'pathVars' => array('module', 'record'),
            'method' => 'foo3',
        ),
        'foo4' => array(
            'reqType' => 'GET',
            'minVersion' => 11,
            'maxVersion' => 12,
            'path' => array('Accounts','config',
            'pathVars' => array('module', 'record'),
            'method' => 'foo3',
        ),
        'foo5' => array(
            'reqType' => 'GET',
            'minVersion' => 14,
            'path' => array('Accounts','config',
            'pathVars' => array('module', 'record'),
            'method' => 'foo5',
        ),
    );
...

With the above definitions, the following table provides the Expected Request for each Endpoint.

 Request URI Best Route 
 /rest/v10/Accounts/config foo1
 /rest/v11/Accounts/config foo3
 /rest/v12/Accounts/config foo4 
 /rest/v13/Accounts/config foo2 
 /rest/v14/Accounts/config foo5 

Last modified: 2019-05-09 19:00:41