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:
Will equate to the URL path of |
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:
The above will pass information to your selected method as the |
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 |