Archives

Ringfree PBX API

The PBX API provides programmatic access to data and actions contained within Ringfree’s fork of FreePBX. Under normal circumstances, the API should always return a response containing the following structure:

  1. An HTTP status code such as 200, 204, 401, 404, etc.
  2. A human readable message such as “OK” or “Not found”.
  3. Whatever data is being returned from the API.

The API supports GET, POST, PUT, and DELETE messages and will return a 405 if the message type is not allowed for a route or a 501 if the message type is not yet implemented for that route.

Access to the API requires HTTP Basic authentication. There is a user and password configured within the API itself (usually api and a random password) and any administrator configured in FreePBX with the ALL SECTIONS permission can use the API.


Using This Documentation

There is a section for each major route with the route URL being the title of the section. Each section is broken down by request type with an example curl command. Example responses are not included in this documentation. Some routes may include an additional URI components at the end so please pay attention to the exact route.

Parameters (including GET query params) are not included in the example commands, but are rather listed with descriptions immediately following the example command. Sections of the example commands in all caps and surrounded by arrows are indented to be replaced with user-supplied data. These are documented along with the parameters except for <USER>, <PASS>, and <SUBDOMAIN> which should remain consistent throughout your use of the API.

It should be noted that the API is also installed on white-labeled PBXs. To access those, simply adjust the URI protocol, subdomain, top-level domain, and second-level domain as necessary.

 


/api/v1/test

GET:

Test route.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/test

POST:

Test route.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/test -X POST

PUT:

Test route.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/test -X PUT

DELETE:

Test route.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/test -X DELETE

 


/api/v1/ampusers

POST:

Creates a new ampuser within FreePBX.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/ampusers/add -X POST

  • username – Required – Alphanumeric but beginning with a letter.
  • password Required Any string.
  • sections Required JSON encoded string consisting of the permissions you’d like to enable for the user. You can check the database directly to get individual permission slugs. Most of them make sense but there are those such as “Add Extension” that present as “999”.

PUT:

Modifies the indicated ampuser within FreePBX.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/ampusers/<USERNAME> -X PUT

  • <USERNAME> – The username of the ampuser to modify.
  • password Required Any string.
  • sections Required As mentioned in the POST route parameters.

DELETE:

Deletes the indicated ampuser within FreePBX.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/ampusers/<USERNAME> -X DELETE

  • <USERNAME> – The username of the ampuser to delete.

 


/api/v1/announcements

GET:

Retrieves data on all announcements within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/announcements

 


/api/v1/cache

POST:

Manages the cache. Currently only reloading of the extension cache has been implemented.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/cache/<CONTEXT> -X POST

  • context – Required  – The context from which to act. Acceptable values: extensions.
  • action – Required – The specific action to perform. Acceptable values: reload.

 


/api/v1/call

POST:

Initiates a call to the specified extension, then bridges that call to another placed to the specified destination.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/call -X POST

  • ext Required The extension you’d like to place the call from.
  • secret Required The SIP secret for the aforementioned extension.
  • dest Required The destination phone number.
  • autoanswer – Optional – Use any truthy value to attempt to automatically answer on supported extensions.

 


/api/v1/callflowcontrols

GET:

Retrieves data on all call flow control toggles within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/callflowcontrols

 


/api/v1/cdr

GET:

Retrieves Asterisk CDR data

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/cdr

  • start – Optional – Unix timestamp beginning limit; Look no earlier than this time. Defaults to beginning of system.
  • end – Optional – Unix timestamp ending limit; Look no later than this time. Defaults to time of request.
  • src – Optional – Source endpoint, if looking for calls originating a particular endpoint. No default value.
  • dest – Optional – Destination endpoint, if looking for calls directed to a particular endpoint. No default value.
  • did – Optional – Limit calls to those containing this DID. No default value.
  • limit – Optional – Maximum number of records to return. Defaults to 1000.

 


/api/v1/coffee

GET:

RFC 2324 implementation.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/coffee

 


/api/v1/conferences

GET:

Retrieves data on all conferences within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/conferences

 


/api/v1/did

POST:

Creates a new DID, or “Inbound Route”, within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/did/add

  • did – Required – The DID to create. Generally speaking this will be an 11 digit NANP number.
  • destination – Optional – Where in the system the DID should be routed. The value here can be any FreePBX destination. The default value is app-blackhole,hangup,1 to terminate the call.
  • mohclass – Optional – The “music on hold” class to associated with the DID. This defaults to default.
  • description – Optional – The description to associate with the DID. If not specified, this will default to the DID itself.

DELETE:

Deletes a DID from the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/did/<DID> -X DELETE

  • <DID> – The DID to delete from the system. This has to be an exact match. For instance ten and eleven digit DIDs are not interchangeable.

 


/api/v1/dids

GET:

Retrieves a list of all DIDs, along with their information, within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/dids

GET:

Retrieves the information about the specified DID from the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/dids/<DID>

  • <DID> – The DID about which to retrieve the information.

 


/api/v1/extdump

GET:

Outputs all information, excluding AstDB data, for all extensions in the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/extdump

GET:

Outputs all information, including AstDB data, for a single extension in the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/extdump/<EXTENSION>

  • <EXTENSION> – The extension for which to retrieve information.

 


/api/v1/extension

POST:

Create a new extension within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/extension/add -X POST

  • extension – Required – The number of the extension you’d like to create.
  • exttype – Required – The extension type. Accepted values: sip, virtual.
  • displayname – Optional – A label to associated with the extension. Defaults to the extension number.
  • secret – Optional – The SIP secret, or password, used to authenticate the extension. This should always be included for SIP extensions but is unused for virtual extensions. Note that software exists which tries to brute force this. As such, this should be a long, random string.
  • outboundcid – Optional – An NANP DID to use for the caller ID when making outbound calls from this extension.
  • emergencycid – Optional – An NANP DID to use for the caller ID when making outbound calls to emergency services from this extension.
  • voicemail – Optional – Use any truthy value to enable voicemail for this extension.
  • email – Optional – Email address to be used for voicemail notification messages.
  • voicemailpassword – Optional – PIN the end user can use to access voicemail and the PBXUser interface.
  • followme – Optional – Use any truthy value to enable FollowMe for this extension.
  • followmegrptime – Optional – Amount of time in seconds to ring the extension before ringing the FollowMe group. Defaults to 20.
  • recInternalIn – Optional – How to address recording internal inbound calls. Accepted values: always, dontcare, never. Defaults to dontcare.
  • recInternalOutOptional – How to address recording internal outbound calls. Accepted values: always, dontcare, never. Defaults to dontcare.
  • recExternalInOptional – How to address recording external inbound calls. Accepted values: always, dontcare, never. Defaults to dontcare.
  • recExternalOutOptional – How to address recording external outbound calls. Accepted values: always, dontcare, never. Defaults to dontcare.
  • recOndemandOptional – Whether on-demand recording should be enabled. Accepted values: enabled, disabled. Defaults to enabled.
  • recPriorityOptional – An integer from 0 to 20 indicating the priority to associate with recordings from this extension. Defaults to 10.

DELETE:

Deletes the specified extension from the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/extension/<EXTENSION> -X DELETE

  • <EXTENSION> – The extension you’d wish to delete.

 


/api/v1/extensions

GET:

Retrieves data for all extensions from the extensions cache. If the cache is empty or invalid, it reloads the cache prior to issuing a response.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/extensions

GET:

Retrieves data for a single extension from the extensions cache. If the cache is empty or invalid, it reloads the cache prior to issuing a response.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/extensions/<EXTENSION>

  • <EXTENSION> – The extension for which to retrieve data.

 


/api/v1/followme

GET:

Retrieves the FollowMe group list for the specified extension.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/followme/<EXTENSION>

  • <EXTENSION> – The extension for which to retrieve the group list.

GET:

Retrieve the FollowMe status for the specified extension.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/followme/<EXTENSION>/status

  • <EXTENSION> – The extension for which to retrieve the FollowMe status.

GET:

Return whether or not the specified entry is within the FollowMe group list.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/followme/<EXTENSION>/<ENTRY>

  • <EXTENSION> – The extension from which to check if the entry is within the FollowMe group list.
  • <ENTRY> – The entry to check for within the FollowMe group liist.

POST:

Performs an action on the FollowMe for the specified extension. With this you can add or remove numbers from the FollowMe group list or enable/disable the FollowMe functionality for the extension altogether.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/followme/<EXTENSION> -X POST

  • <EXTENSION> – The extension upon which to perform the action.
  • action – Required – The action to perform. Accepted values: addExternal, removeExternal, enable, disable.
  • number – Conditional – The number to add/remove from the FollowMe group list. This parameter is required for the addExternal and removeExternal actions but is ignored for the enable and disable actions.

 


/api/v1/ivrs

GET:

Retrieves data on all IVRs (auto-attendants) within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/ivrs

 


/api/v1/patterns/outbound

GET:

Retrieves a list of outbound dial patterns configured for use within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/patterns/outbound

 


/api/v1/pbx/asterisk

GET:

Performs a core reload command within Asterisk.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/asterisk/reload

 


/api/v1/pbx/framework

GET:

Retrieves a list of all destinations within the phone system. This includes extensions, ring groups, conferences, queues, etc. Specifically what is considered to be a destination is determined within the various FreePBX modules so there specific output here depends on which modules are enabled.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/framework/destinations

GET:

Retrieves a list of just the numerical assignments for all destinations within the phone system. As noted in the above destinations documentation, what is included is dependent on which modules are enabled.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/framework/destnumbers

GET:

Retrieves a boolean indicating whether or not the FreePBX configuration requires a reload. This is commonly indicated within the GUI with the presence of a red “Apply Config” button.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/framework/needreload

GET:

Returns a list of available “Music on Hold” classes configured within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/framework/mohclasses

GET:

Performs a reload of the FreePBX configuration. This applies any “pending” changes, writes the Asterisk dial plan, and removes the red “Apply Config” button from the GUI.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/framework/reload

POST:

Sets a flag within FreePBX indicating that the configuration requires a reload. This presents within the GUI with the presence of a red “Apply Config” button.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/pbx/framework/needreload -X POST

 


/api/v1/queues

GET:

Retrieves data on all queues within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/queues

 


/api/v1/recordings

GET:

Retrieves a list of all system recordings within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/recordings

GET:

Downloads the specified recording from the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/recordings/<NAME>

  • <NAME> – The display name of the recording you wish to download.

 


/api/v1/registrations

GET:

Retrieves information, including registration status and user agent, for all extensions in the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/registrations

GET:

Retrieves information, including registration status and user agent, for the specified extension.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/registrations/<EXTENSION>

  • <EXTENSION> – The display name of the recording you wish to download.

 


/api/v1/ringgroups

GET:

Retrieves data on all ring groups within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/ringgroups

 


/api/v1/state

GET:

Retrieves state/presence data on all SIP extensions within the system.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/state

GET:

Retrieves state/presence data on the specified SIP extension.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/state/<EXTENSION>

  • <EXTENSION> – The extension for which to retrieve state/presence data.

 


/api/v1/timeconditions

GET:

Retrieves data on all time conditions within the system. Associated time group data is included.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/timeconditions

 


/api/v1/voicemail

GET:

Retrieves a list of all voicemail messages for the specified extension.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/voicemail/<EXTENSION>

  • <EXTENSION> – The extension from which to retrieve voicemail message data.

GET:

Downloads a specific voicemail message.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/voicemail/<EXTENSION>/<MESSAGE>

  • <EXTENSION> – The extension from which to download the message.
  • <MESSAGE> – The voicemail message file identifier.

POST:

Delete one or more voicemail messages from the specified extension.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/voicemail/<EXTENSION> -X POST

  • <EXTENSION> – The extension from which to delete the voicemail message(s).
  • messages – Required – A multipart array of message file identifiers.

DELETE:

Deletes the specified voicemail message.

curl -u <USER>:<PASS> https://<SUBDOMAIN>.ringfree.biz/api/v1/voicemail/<EXTENSION>/<MESSAGE> -X DELETE

  • <EXTENSION> – The extension from which to delete the message.
  • <MESSAGE> – The voicemail message file identifier.

 

4. Assembling views

All the functionality in the world is useless if you don’t have some method of interfacing with it. For this reason, the portal has a powerful and extensible way of implementing views. Views an be implemented very simply, however the portal has a built in framework for handling most of the tedious bits for you. Let’s start with a basic view example and go from there.

Implementing a “Hello World” View

Fundamentally a view can be implemented with only two things: a registration to the VIEW hook, and a function that outputs some content:

namespace Papal\View;
__register('VIEW', '/hello', ['callback' => '\Papal\View\Hello::display']);

class Hello {
  public static function display() {
    echo "Hello World";
  }
}

In this case, visiting the /hello route within your application will result in the application displaying the output of the specified callback function. If you navigate to the route in your browser you’ll see just the following:

Hello World

Pretty simple stuff, right? That said, there’s a bit more going on under the hood. To demonstrate, let’s update our function definition a bit:

namespace Papal\View;
__register('VIEW', '/hello', ['callback' => '\Papal\View\Hello::display']);

class Hello {
  public static function display($args) {
    var_dump($args);
  }
}

Now if you navigate to the view, you’ll be presented with the following output:

array(1) {
  ["urlpath"]=>
  array(1) {
    [0]=>
    string(5) "hello"
  }
}

As you can see, the components of the URL get passed to the function so that the function can act upon them.

Regular Expressions in URLs

One feature of the VIEW registration hook is that regular expressions can be used as URL components. Consider the following example:

namespace Papal\View;
__register('VIEW', '/hello/([A-Za-z]+)', ['callback' => '\Papal\View\Hello::display']);

class Hello {
  public static function display($args) {
    echo "Hello, {$args['urlpath'][1]}";
  }
}

Now if we navigate to /hello/George we’ll be presented with the following output:

Hello, George

The ViewHandler Class

The above is all good and well if you have extremely basic needs from your view. But what if you want your view to actually integrate with the rest of the application? Or what if you have certain permissions requirements for the view? For this we have the \Papal\ViewHandler class.

The Viewhandler class is intended to serve as a parent class for your views. By extending it, you give your class support for authentication and permissions requirements, and a nifty way of mapping arrays to your view object’s properties. The ViewHandler class is also the “correct” way of implementing views so that potential future updates to it will affect all of your views without you having to modify each of your views. Let’s take a look at a basic implementation that uses the portal’s Body template:

namespace Papal\View;
__register('VIEW', '/hello/([A-Za-z]+)', ['callback' => '\Papal\View\Hello::display']); 

class Hello extends \Papal\ViewHandler {
    public function __construct($args = null) {
        $setup = $this->setup($args);
        if ($setup === true) {
            \Papal\Template\Body::open("Hello World", "Hello World");
            $this->markup();
            \Papal\Template\Body::close();
        }
    }

    private function markup() {
        echo "Hello, {$this->urlpath[1]}";
    }
}

As you can see, we’re extending the ViewHandler class and declaring a constructor overload that accepts a single argument (which defaults to null). The constructor then calls the setup() object method from the ViewHandler class which determines whether to display the the view, a login page, or a 404 page. If there are no issues with the setup, the constructor then displays the output of the markup() object method in between the opening and closing of the Body template.

The private markup() object method in this case simply outputs the same string from our more basic example above, but note that this time it’s accessing urlpath as an object property rather than as part of an array passed directly to the display() method (which is now defined within the ViewHandler class and is simply a shortcut to call the object constructor). The setup() method mentioned above, takes the entries in that array and creates object properties out of them.

So what about authentication and permissions? By default all classes extending the ViewHandler class require the user to be logged in and don’t have specific permissions requirements, but this is very easy to set. Let’s take our class definition from above and make the requirements explicit and add the requirement that the user have the search capability:

class Hello extends \Papal\ViewHandler {
  public function __construct($args = null) {
    $this->requiresAuth = true;
    $this->requiresCaps = 'search';
    $this->requiresAllCaps = false;

    $setup = $this->setup($args);
    if ($setup === true) {
      \Papal\Template\Body::open("Hello World", "Hello World");
      $this->markup();
      \Papal\Template\Body::close();
    }
  }

  private function markup() {
    echo "Hello, {$this->urlpath[1]}";
  }
}

Let’s break it down:

  • requiresAuthboolean – Whether or not the view requires the user to be authenticated. For most views, you’ll want to leave this alone, however you may find it useful to override this if you have pages you wish to display publicly or if you’re, for instance, building an API route that authenticates differently.
  • requiresCapsstring | array – If this is a string, the user accessing the page must have the listed capability in the permissions list for his/her user role. If this is an array, the user accessing the page must have one of the capabilities in the permissions list for his/her user role.
  • requiresAllCapsboolean – Makes it so that the user must have all the capabilities in the requiresCaps array, rather than any single one, in order to access the view.

Structuring your HTML

While there are no specific requirements for HTML structuring, there are definitely a few best practices and a few things to keep in mind. At the moment and for the foreseeable future, the portal’s default templates make use of Bootstrap 4 and it’s generally a good idea to use it in your views as well. In fact the Bootstrap CSS and Javascript are both included in the default Header template (which is called from within the Body template). If your view requires custom CSS you can add it by passing the necessary argument to the \Papal\Template\Body::open() function or by including style tags in the view output.

Some modules provide custom registration hooks which can be used to assist with and/or automate the construction of various HTML components. For instance the PapalAdmin module provides the ability to quickly and easily assemble forms, tables, modals, and jQuery form submissions by using arbitrarily defined registration hooks.

In some rare cases, you may find that the default templates included in the portal’s core code are not sufficient for what you’re trying to accomplish. In this case you’re welcome to implement your own templates or assemble your own views from scratch. A good use case for this would be implementing publicly viewable pages with a different “theme” than the administrative back-end. You could also use some logic within the view to display different templates based on other factors such as the user ID, the time of day, or the value of a setting in the database.

As an example let’s say a developer has a user base that is still largely dependent upon an old version of Internet Explorer for reasons beyond their control. The developer could write new templates that work with the old IE version and then test the value of $_SERVER['HTTP_USER_AGENT'] to determine which templates to load.

2. References, metadata, and the database

The data and object implementation within the portal generally follows one simple rule: make everything as abstract as possible. The more abstract it is, the easier it should be to extend and because none of the data in the portal is speed critical, we can afford to trade some amount of optimization for flexibility.

Data in the portal is almost always abstracted into two categories: references and metadata. The former containing no more than an ID and a type, the latter containing all of the information associated with that reference. For example, let’s say we need some way to represent a vehicle. The reference would simply contain an automatically assigned ID, and the type vehicle while the metadata would include things such as the make, model, color, year, mileage, and other pertinent bits of data. To better understand this, let’s take a look at the database schemas for each type. Here is the refs table schema:

+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| type  | varchar(128)     | NO   | MUL | NULL    |                |
+-------+------------------+------+-----+---------+----------------+

And here is the meta table schema:

+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| type  | varchar(128)     | NO   | MUL | NULL    |                |
| slug  | varchar(128)     | NO   | MUL | NULL    |                |
| refid | int(10) unsigned | NO   | MUL | NULL    |                |
| value | longtext         | YES  |     | NULL    |                |
+-------+------------------+------+-----+---------+----------------+

Using our above example, let’s presume we need to store data that represents a 1994 Honda Civic. Once created let’s say we have an id of 42. Here would be the example reference and metadata as stored in the database:

+----+---------+
| id | type    |
+----+---------+
| 42 | vehicle |
+----+---------+

+------+---------+-----------+-------+---------+
| id   | type    | slug      | refid | value   |
+------+---------+-----------+-------+---------+
| 3121 | vehicle | type      |    42 | vehicle |
| 3121 | vehicle | make      |    42 | Honda   |
| 3122 | vehicle | model     |    42 | Civic   |
| 3123 | vehicle | year      |    42 | 1994    |
| 3124 | vehicle | color     |    42 | green   |
| 3125 | vehicle | mileage   |    42 | 127235  |
| 3126 | vehicle | trimLevel |    42 | CX      |
+------+---------+-----------+-------+---------+

So why not create a vehicles table with the relevant columns? The answer is that in this paradigm, anyone or anything can then add more metadata without there having to be any changes to the original code or to the database structure, and all the base level data access and manipulation functions can be contained within a couple of classes. Also note that the indexes in the meta table schema should provide sufficient performance for most use cases.

Of course there’s nothing stopping a developer from creating their own database tables if they require optimization beyond the reference/metadata paradigm. Creating and dropping those tables would be a good use case for the onActivate and onDeactivate parameters specified in the module’s meta.txt file.

There are classes specifically for interacting with the database and for handling references and metadata. Unless you have very particular needs, it’s extremely unlikely that you’ll ever need to interact with the \Papal\Database class directly. Rather most everything written to or read from the database will happen via the \Papal\Reference class, a child of it, or the \Papal\Meta class.

The Reference Class

Almost every “thing” that you’ll represent within the portal is done so via the \Papal\Reference class or a child class which extends it. If you look in the code of most existing modules, you’ll notice that there are many instances of classes extending the Reference class to provide implementation specific functionality or to override the default functions. The Reference class or an extension of it provides a clean and simple way of creating just about any sort of object representation within the portal.

The fundamental anatomy of a Reference object and how it maps to the database is incredibly simple: the reference object represents an item in the refs table and all of the object properties represent the associated items in the meta table. Consider the following example:

$ref = \Papal\Reference::new('vehicle');
$ref->make = 'Honda';
$ref->model = 'Civic';
$ref->year = '1994';
$ref->save();
echo $ref->id;

This creates a new reference of type vehicle. Note that the new() static method writes the reference to the database and returns the object with a couple of default properties: id and type. Next we set a few of the values used in our previous example and call the save() object method to write the object properties to the database as metadata.

It’s extremely unlikely that you’ll ever need to call the Reference class constructor directly. Doing so without the reference ID will simply return an empty object. Creating new Reference objects is almost always done via the aforementioned new() static method and retrieving existing ones from the database is almost always done with the constructFromId() static method as is done in the following example:

$car = \Papal\Reference::new('vehicle');
$car->make = 'Chevrolet';
$car->save();

$chevy = \Papal\Reference::constructFromId($car->id);
var_dump($car);
var_dump($chevy);

The above will result in the following output:

object(Papal\Reference)#8 (3) {
  ["id"]=>
  string(3) "625"
  ["type"]=>
  string(7) "vehicle"
  ["make"]=>
  string(5) "Chevy"
}
object(Papal\Reference)#7 (3) {
  ["id"]=>
  string(3) "625"
  ["type"]=>
  string(7) "vehicle"
  ["make"]=>
  string(5) "Chevy"
}

Note that you now have two objects that represent the same reference. While you’d probably not want to do this in the module you’re planning to write, it serves as an appropriate example of how to create and access Reference objects.

There are additional static methods and object methods for accomplishing various tasks with and within the Reference class. There are methods available to retrieve multiple objects, delete/purge objects, set timestamps, set object properties from JSON data, etc. For a complete reference please look at the code on Github.

Extending the Reference Class and Using Utility Classes

So what if your “thing” needs some additional functionality beyond what is found in the Reference class? Or what if all of your references have certain metadata values that need to be associated with them? There are two ways to accomplish these sorts of things. Consider the following example of a class that extends the Reference class:

namespace Papal;
class HondaCivic extends Reference {

  public static function new($placeholder = null) {
    $ref = parent::new('vehicle');
    $ref->make = 'Honda';
    $ref->model = 'Civic';
    return $ref;
  }

  public function setMileage(int $miles) {
    $this->mileage = $miles;
  }
}

And this example of a separate utility class:

namespace Papal;
class HondaUtility {

  public static function new() {
    $ref = Reference::new('vehicle');
    $ref->make = 'Honda';
    return $ref;
  }

  public static function newCivic() {
    $ref = self::new();
    $ref->model = 'Civic';
    return $ref;
  }

  public static function setMileage(Reference $ref, int $miles) {
    $ref->mileage = $miles;
  }
}

Both classes effectively accomplish the same things and one implementation is not necessarily better than the other. If you prefer a more object-oriented coding style, extending the Reference class might make more sense to you. If you prefer a more procedural approach, a utility class might suit your workflow better.

The Meta Class

While most of your interactions with metadata will be via Reference object properties, sometimes you may find that it makes sense to interact with the metadata directly. The \Papal\Meta class exists for this reason. There are also various static methods for retrieving reference IDs or multiple pieces of metadata. The Meta class isn’t intended to be an abstracted away, “lower level” class in the same vein that \Papal\Database is, rather it’s intended to be used anytime you need to manipulate or filter for metadata but when you don’t necessarily want or need the extra overhead of Reference objects. In fact the Reference class itself makes use of the Meta class for a lot of underlying functionality.

Say for instance we want an array of all references that represent green vehicles. We can leverage the Meta class to assist with this:

$refIds = \Papal\Meta::getRefIdArray('vehicle', 'color', 'green');
foreach ($refIds as $id) {
  $refs[] = \Papal\Reference::constructFromId($id);
}

Or let’s say we need an array of all vehicle colors currently in the database perhaps to display in something like a select input or a report:

$colorData = \Papal\Meta::getRefIdsAndValues('vehicle', 'color');
$colors = array_unique(array_values($colorData));

It should be noted that the Meta class is a final class and that it only contains static methods. Basically there’s no reason to ever try and instantiate a Meta object as metadata should only ever exist as primitives and Reference object properties. For a complete Meta class reference, check out the code on Github.

Next we’ll take a look at the portal’s powerful registration hooks, how they work, and how to use them:

Understanding and using registration hooks.

3. Understanding and using registration hooks

Portal development makes heavy use of a concept called registration hooks. Say you need to create a new view, or add an item to the menu, or insert some javascript code into the header? The answer is to do so via registration hooks. There are a handful of default hooks you can make use of, but it’s really simple to create your own custom hooks to do with whatever you like.

Portal Registration Hook Reference

Let’s look at an example from the Homepage module:

__register('VIEW', "/", ['callback' => '\Papal\View\Homepage::display']);

Note that we’re calling the __register() function with three arguments. The first is the registration hook, in this case we’re registering a new view. The second is the trigger to be registered, in this case we want to the view to be displayed at / or the base URL for the application.

The third argument is where the real magic happens. Different registration hooks will have different usage requirements, but this is basically an array of data that tells the hook how to operate. In the case of the VIEW hook, the only thing necessary is a callback which is the function that will output the HTML for the view.

Let’s take a look at another example, this time from /templates/Sidebar.php in the portal core code:

__register('FORM', 'ajaxDisplayMenu', ['callback' => '\Papal\Menu::setDisplayMenuCurrent', 'args' => ['value']]);

In this case we’re registering a form handler with the ajaxDisplayMenu trigger. How this works is actually quite simple: Whenever a POST request is submitted to the application that contains a value for the ajaxDisplayMenu key, it executes the specified callback with the specified arguments using the appropriate POST values. The above would functionally do the same as the following:

if (isset($_POST['ajaxDisplayMenu'])) {
    \Papal\Menu::setDisplayMenuCurrent($_POST['value']);
}

So why use this paradigm instead of something more conventional? The answer is simple: registration hooks make it very easy to build extensible features. Because of how they’re implemented any developer can, for example, register a new menu item from any PHP file that gets evaluated in their module.

For another example, say a dev named Jim uses registration hooks to build a table for displaying CDR data. Later on another dev named George could then add columns to Jim’s table to display additional data that wasn’t part of the original scope. And George can do it barely having to look at Jim’s code, much less modify it in any capacity.

How it Works

So let’s get under the hood and take a look at how the __register() function works so you can learn how to better make use of registration hooks and even implement your own. In the portal’s index.php the function is declared as follows:

function __register($hook, $trigger, $args = true) {
    $GLOBALS["_PAPAL_{$hook}"][$trigger] = $args;
}

As you can see, the __register() function is simply a clean way of leveraging the PHP superglobal variable called $GLOBALS. All of the code that actually works with registration hooks utilizes $GLOBALS directly. As an example, here is part of the \Papal\Form::handle() function:

foreach ($GLOBALS["_PAPAL_FORM"] as $trigger => $form) {
    if (isset($_POST[$trigger]) || isset($_FILES[$trigger])) {
        ...
    }
}

Custom Registration Hooks

So how would you go about implementing a custom trigger? Simple, just write a function that uses the registration hook paradigm and call it from somewhere. Here is an example:

namespace Papal;
class LogRegister {
  public static function logIt() {
    foreach ($GLOBALS["_PAPAL_LOG"] as $trigger => $data) {
      Log::write("{$trigger} -- {$data['content']}");
    }
  }
}

Here would be the registration hook:

__register('LOG', 'test', ['content' => 'Success!']);

And the registration hooks would be processed anywhere in your module where you call the \Papal\LogRegister::logIt() function.

Overriding Hooks

It’s possible to override registration hooks in some cases. Say for instance the author of the Homepage module want a particular menu item to display on the home page. Remember our example from above where we saw that the Homepage module uses a VIEW registration hook to display the result of the \Papal\View\Homepage::display() function? Let’s say the author doesn’t want to display the item for /infrastructure from the Infrastructure module. All the author has to do is to place the following within the \Papal\View\Homepage::display() function before the HTML is output:

__register('MENU', "/infrastructure", []);

It’s advisable to not try and override registration hooks from within the global namespace. Rather you should only ever attempt a registration hook override from within your function, which should be in a properly namespaced class.

Conventions

The correct convention for assembling and using a registration hook is to pass an uppercase string for the $hook value, a non-uppercase or mixed-case string for the $trigger value and an associative array for the $args value, even if you only require a single value from $args. You should always pass the value for $hook explicitly though it’s perfectly acceptable to use the result of a function for $trigger and/or $args. Just be sure the function is loaded and available for your registration hook.

If you implement custom registration hooks within your module, please document them in the README.md file so that other developers can easily know that they’re available and how to make use of them.

Content Hooks

In various places through the portal’s templates and views, you may encounter functions similar to the following:

\Papal\Hooks::insert('SIDEBAR_JS_BEFORE');

This is what is referred to as a content hook. Basically a content hook is a simple way to add content to areas of a page. They’re simple to use and they’re even in use within the portal core code itself.

So how do they work? Simple, just call make a registration containing three arguments: a callback, an optional array of args, and an optional priority. The callback will obviously be a string containing a namespaced function name. The args will obviously be the arguments passed to that function. The priority is the order in which the content displays if there are multiple registrations to the same content hook.

As an example, let’s start by defining a function:

namespace Papal;
class HookTest {
  public static function hookDemo() {
    ?>
      <!-- TESTING -->
    <?php
  }
}

Now let’s assemble the registration:

__register('SIDEBAR_JS_BEFORE', 'hookdemo', ['callback' => '\Papal\HookTest::hookDemo']);

The result will be the comment output by the function appearing in the page where the SIDEBAR_JS_BEFORE hook is called using the \Papal\Hooks::insert() function mentioned above.

Now that you have an understanding of how registration hooks work, let’s put them to use and assemble a view:

Assembling views.

1. Directory naming, creating a meta.txt file, and ModuleInfo objects

As stated in the introduction, two parts of a Portal module that absolutely must be present are a parent directory and a file named meta.txt. The directory name must be a proper slug, consisting only of lowercase letters, the digits 0-9, and hyphens. In addition to general information such as the name, description, and author of the module, meta.txt contains functionally relevant values such as the module version and dependencies, as well as activation and deactivation instructions.

The format of meta.txt is very simple, yet strictly enforced. Let’s take a look at an example:

# Copyright 2018 Ringfree Communications Inc.

moduleName:: Demonstration Module
moduleDesc:: Provides "proof of concept" functionality for development and testing purposes.
moduleIcon:: bookmark
authorName:: Kendall Weaver
authorEmail:: kendall@ringfree.com

version:: 1.0.0
license:: WTFPL
section:: none
depends:: papaladmin>=0.0.1,homepage>=0.0.1,CORE>=0.0.1

onActivate:: \Papal\DemoModule::activate
onDeactivate:: \Papal\DemoModule::deactivate

Note the very first line beginning with a # character. This is a comment. Any line beginning with a # will be ignored when the file is evaluated. To keep things simple, inline and multiline commenting are not supported.

The remaining lines adhere to a very simple syntax: the name of the field immediately followed by two instances of the : character, immediately followed by a single space. Failure to strictly ahere to the name:: value paradigm will result in the line not being correctly parsed when the file is evaluated.

When the file is evaluated, what happens “under the hood” is that a \Papal\ModuleInfo object is created and each field is stored as a class-level public variable. Additionally three other class-level public variables are stored: slug being a string containing the name of the directory housing the module code, path being a string containing the file path to the module directory, and active being a boolean representing whether or not the module has been activated. More on this later.

The Fields

  • moduleNameRequired Field – A user-readable name for the module. Feel free to use capitalization, punctuation, numbers, and special characters if they’re necessary to properly name the module.
  • moduleDesc – A user-readable description for the module. Like the name, feel free to take liberties regarding the characters used.
  • moduleIcon – A reference to which icon should be displayed beside the module information on the module administration page. By default the Portal makes use of Font Awesome and supported values for this field are any icon in the solid section of the free set.
  • authorName – The name of the module author. Convention is to use a full, real name.
  • authorEmail – The email address of the module author. Please use a valid email address.
  • versionRequired Field – The module version number. This is used for dependency checking and, as such, it is advisable (though not required) to keep the versioning scheme in line with other modules. Semantic versioning is strongly suggested.
  • license – Presumably all modules written for the Portal will be owned by Ringfree with all rights reserved, however the core itself is fairly application agnostic and could potentially be licensed to other companies and/or open-sourced in the future.
  • section – Simply a means of organizing modules within the module administration interface.
  • depends – A comma separated list of other modules required to be active before this module can be activated. Note that the list also includes comparators and version numbers. Each listed dependency must have a comparator and version number. If a specific version is not required, use >=0 as this should encapsulate all possible versions. Standard comparators are supported: >, <, >=, >=, ==, and !=. Also note that specific version requirements for the Portal itself can be specified by using CORE as the dependency name.
  • onActivate – This allows you to specify a function (with no arguments) to be evaluated when the module is activated. The function can be (and in most cases should be) within the module itself. Note that the namespace and class must be included if applicable. Please do not include parenthesis at the end.
  • onDeactivate – Similar to above, this allows you to specify a function to be evaluated when the module is deactivated. Again no arguments will be passed to the function, the namespace and class must be included, and parenthesis must be omitted.

Note that there are only two required fields: moduleName and version. Also additional arbitrary fields may be specified and they’ll likewise be evaluated into class-level public variables. A potential use case would be making additional metadata available to other modules.

Creating ModuleInfo Objects

The ModuleInfo class exists within the Papal namespace and has a constructor that accepts a single argument: the module slug (the name of the directory where the meta.txt file and the module code is stored). Presuming our above example file exists within a directory named demomodule, the ModuleInfo object would be created as follows:

$x = new \Papal\ModuleInfo('demomodule');

ModuleInfo objects are primarily used to construct the module administration interface, itself part of the papaladmin module. When instantiated, the constructor function explicitly sets the active, path, and slug variables before calling the private parseMeta function which evaluates the data in meta.txt. A missing meta.txt file or the absense of required fields will result in an Exception. Additionally attempting to call the constructor with a reserved value such as CORE will result in an exception. At present CORE is the only reserved value. This page will be updated with any additional reserved values added in the future.

Now that you have a basic understanding of how to put together the meta.txt file, let’s take a look at how the portal handles data:

References, metadata, and the database.

0. Module Development for the Ringfree Portal

The Ringfree Communications Portal, internally designated as “Papal” or “Papal Mainframe” supports extensible functionality by way of installable modules. Module development is a fairly straightforward process and should be accessible to anyone with PHP experience in a capacity beyond the introductory.

The Portal is implemented targeting PHP 7.0, which should be the minimum target version for all module code for the time being. There are and will be no plans to support older 5.x versions of PHP. The Portal uses a fairly standard LAMP application stack with Apache’s mod_rewrite and mod_headers enabled. With minimal configuration, the Portal could be deployed in a number of similar environments such as LEMP, WAMP, etc.

Portal modules, at a minimum, require two components: a directory with a unique name limited to alphanumeric characters and hypens, and a file within that directory named “meta.txt” containing some basic information about the module. Such a module would accomplish nothing other than a demonstration of proof-of-concept, but functionally that’s all that is required to implement a module.

To add functionality to the module, all files within the module directory ending with a .php extension will be evaluated. As such, a module developer has some options regarding how to structure the project. For a simple module it may make sense to place all of the code in one or two PHP files directly beside the meta.txt file in the module directory. For a more complex module it would perhaps be better to organize the code in subdirectories and have a single base level PHP file that includes the appropriate files from the subdirectories. The only “correct” way to approach this is the one that makes the most sense given the scope of the module being worked on.

The Portal’s core code makes use of programming concepts such as namespaces, recursion, inheritance, and static functions. It’s advisable for any module developer to be reasonably familiar with these concepts and to implement them appropriately within their module. In fact some functionality requires classes to exist within specific namespaces (these situations will be pointed out later).

What follows is a guide that will walk you through the development of a module including structuring the meta.txt file, making use of the Portal’s registration features, and implementing a view. To start, let’s take a deeper look at the basic building blocks:

Directory naming, creating a meta.txt file, and ModuleInfo objects.

Blacklist Numbers

You can blacklist a number by dialing *30. When prompted, enter the number you would like to blacklist.

You may remove a number from the blacklist by dialing *31 and then dialing the number to be removed when prompted.

To blacklist the last caller simply dial *32.

Ringfree Node API

Ringfree Node API

Overview

Access API for PBX and Container Management.
Datacenter routes are accessed by means of the reverse proxy set up in that DC. Currently there are only two:

atl.ringfree.biz
dfw.ringfree.biz

To connect with the nodes in that datacenter, you will use the the ‘n##’ designation for that node. For instance, node 1 in ATL would require that you use this url:

atl.ringfree.biz/n01/

and similarly for DFW:

dfw.ringfree.biz/n07/

Access to the storage nodes in the datacenter only requires changing to the ‘s##’ designation, i.e.:

atl.ringfree.biz/s01/
dfw.ringfree.biz/s01/

Below are the available routes on the nodes. For brevity, the route will be shortened to /api/v2/…, with the assumption that the above rules are followed and applied in front of this route, i.e.:

atl.ringfree.biz/n01/api/v2/...

GET Routes:

/api/v2/test

Simple GET test route. Returns JSON enpred ‘200’ response.

  • Expected Return:
    {"status":200,"message":"Success","data":{"gettest":"success"}}

/api/v2/ctid/$ctid/

$ctid here is the actual Container ID. Returns status check for container if it exists on that node.

  • Expected Return (container exists):
    {"status":200,"message":"Success","data":{"CTID":"1900666","NPROC":"6","STATUS":"running","IP_ADDR":"172.16.67.207","HOSTNAME":"butthole.ringfree.biz"}}
  • Expected Return (container does not exist):
    {"status":404,"message":"Container Does Not Exist","data":null}

/api/v2/ctid/$ctid/nimbus

$ctid here is the actual Container ID. Returns status check for container’s Nimbus mount directories. Checks if dir extists and that it’s at least the base/vanilla size.

  • Expected Success Return (Nimbus Ready to Mount):

    {"status":200,"message":"Nimbus Ready for Mount","data":null}

  • Expected Failure Return (Nimbus does not exist):

    {"status":404,"message":"Nimbus Directory Missing","data":null}

  • Expected Failure Return (Nimbus not fully Sync’d):

    {"status":409, "message":"Nimbus Still Syncing","data":null}

POST Routes:

/api/v2/test "action=test"

Simple POST test route. Returns JSON enpred ‘200’ response.

  • Expected Return:
    {"status":200,"message":"Success","data":{"posttest":"success"}}

/api/v2/ctid/$ctid/ "action=$action"

This route manages the container itself. $ctid is the container’s ID and $action what what you are doing to the container. Currently supported actions: ‘create’, ‘start’, ‘stop’, ‘restart’.

  • $action = ‘create’:
    Creates the container. Actual function call changes depending on the type of node. SQL node runs stage 1, Storage Nodes run stage 2 and stage 3, and host node runs stage 4. Create requires 8 other parameters before being able to complete successfully: type, hostname, ip, rootpw, adminpw, subdomain, vendorid, and dbhost.

    • Expected Success Return:
      {"status":200,"message":"Success","data":{"result":"complete"}}
    • Possible Failure Responses:
      • if type parameter does not match sql, stor, nimbus, nor node:
        {"status":400,"message":"Action Type Not Recognized","data":null}
      • if physical node not set up for that type/stage:
        {"status":501,"message":"Not Implemented","data":null}
      • If missing any of the import parameters (message will provide list of missing parameters) and data will be JSON enpred POST parameters provided:
        {"status":400,"message":"Missing Type, Hostname, IP, ...","data": {}}
    • $action = ‘start’:
      Mounts and starts the container on node.

      • Expected Success Return:
        {"status":200,"message":"Success","data":{"result":"Container start in progress..."}}
      • Expected Failure Return:
        {"status":409,"message":"Container Already Running","data":null}
    • $action = ‘stop’:
      Stops and unmounts container on node.

      • Expected Success Return:
        {"status":200,"message":"Success","data":{"result":"Container is unmounted"}}
      • Expected Failure Return:
        {"status":409,"message":"Container already stopped","data":null}
    • $action = ‘restart’:
      Restarts running container on node.

      • Expected Success Return:
        {"status":200,"message":"Success","data":{"result":"Container start in progress..."}}
      • Expected Failure Return:
        {"status":409,"message":"Container Not Running","data":null}

    /api/v2/ctid/$ctid/services/ "action=$action"

    This route is for managing the services running on a container. $ctid is the container’s ID and the $action is how you want the services to change. Currently supported actions: ‘start’, ‘stop’, ‘chown’.

    • $action = ‘start’:
      Starts the services on the container.

      • Expected Success Return:
        {"status":200,"message":"Success","data":{"result":"complete"}}
      • Expected Failure Return:
        {"status":409,"message":"Asterisk already running","data":null}
    • $action = ‘stop’:
      Stops the services on the container.

      • Expected Success Return:
        {"status":200,"message":"Success","data":{"result":"complete"}}
      • Expected Failure Return:
        {"status":409,"message":"Asterisk already stopped","data":null}
    • $action = ‘chown’:
      Sets proper permissions on the container.

      • Expected Success Return:
        {"status":200,"message":"Success","data":{"result":"complete"}}
      • Expected Failure Return:

        Not applicable; Chown should complete regardless of services running or not. If error encountered, likely related to $ctid value and not the $action.

Papal REST API v2 Reference

Version 2 of the Papal REST API improves on version 1 in a number of ways. Most notably it always returns a consistent data structure with three components:

  1. An HTTP status code such as 200, 204, 401, 404, etc.
  2. A human readable message such as “OK” or “Not found”.
  3. Whatever data is being returned from the API.

Additionally, version 2 adds support for POST requests.

Like version 1, the API requires authentication in the form of both a username and password. Access to the various GET and POST routes depends on whether or not the user has the api-get or api-post permissions assigned to them in Papal. As with all other Papal functions, superadmins can access both without having the permissions explicitly set.

 


https://portal.ringfree.com/api/v2

This is used as a test route for both GET and POST requests. When used as a POST route, all POST data is discarded as it’s just a route for testing.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/

Example response:

{"status":200,"message":"GET test success","data":null}

POST

Example usage:

curl -k -u user:pass -d "test=test" https://portal.ringfree.com/api/v2/

Example response:

{"status":200,"message":"POST test success","data":null}

 


https://portal.ringfree.com/api/v2/manifest

This route returns a complete manifest of PBX containers indexed by CTID. This is a GET only route and POST requests to it will result in a 405 “Method not allowed” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/manifest/

Example response (abbreviated):

{"status":200,"message":"OK","data":{"4005":{"ipaddress":"172.16.67.18","hostname":"privtest1.ringfree.biz","node":"node001.ringfree.biz","dbserver":"db1.ringfree.biz","callperseclimit":"15","chanlimit":"30","callingplan":"65533","pstnroutetable":"3","sbcroutetable":"4","sbctrunkid":"4","fqdntrunkid":"5","pstntrunkid":"6","pptlocation":null,"fiftyseventy":null,"vendorid":"1","fop2manager":null,"dbname":"4005_asterisk","nfsserver":"atl.san1.ringfree.biz"}}}

 


https://portal.ringfree.com/api/v2/revnat

This route returns an abbreviated PBX container manifest containing data specifically useful for the Revnat and HAProxy instances in the Ringfree infrastructure. This is a GET only route and POST requests to it will result in a 405 “Method not allowed” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/revnat/

Example response (abbreviated):

{"status":200,"message":"OK","data":{"4005":{"hostname":"privtest1.ringfree.biz","ipaddress":"172.16.67.18","vendorid":"1"}}}

 


https://portal.ringfree.com/api/v2/customers

This route returns a complete list of customers in the Papal database along with all of the associated contact data. This is a GET only route and POST requests to it will result in a 405 “Method not allowed” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/customers

Example response (abbreviated):

{"status":200,"message":"OK","data":{"1":{"name":"AcctVantage ERP","address1":"317 N. Main St.","address2":"Suite 200","city":"Hendersonville","state":"NC","zip":"28792","hostname":"acctvantage.ringfree.biz","phone":"18286923301","itpartnerid":null,"isitpartner":null,"active":"1"}}

 


https://portal.ringfree.com/api/v2/contacts

This route returns a complete list of contacts in the Papal database along with all of the associated contact data. This is a GET only route and POST requests to it will result in a 405 “Method not allowed” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/contacts

Example response (abbreviated):

{"status":200,"message":"OK","data":{"58":{"customer":"Edifice Solutions","customerid":"61","firstname":"Kisha","lastname":"Baker","email":"k.baker@edificesolutions.com","phone":"12024995900","extension":"104","type":"none"}}}

 


https://portal.ringfree.com/api/v2/contacts/<customer_id>

This returns a list of contacts associated with any given customer. Replace <customer_id> with the actual customer ID value in the URL. This is a GET only route and POST requests to it will result in a 405 “Method not allowed” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/contacts/61

Example response:

{"status":200,"message":"OK","data":{"58":{"customer":"Edifice Solutions","customerid":"61","firstname":"Kisha","lastname":"Baker","email":"k.baker@edificesolutions.com","phone":"12024995900","extension":"104","type":"none"}}}

 


https://portal.ringfree.com/api/v2/contact

This route is for submitting a new contact and, as such, is a POST only route. The following fields will be considered when submitted, however customer, firstname, and lastname are required:

  • customer (int) – The ID of the customer with which the contact should be associated.
  • firstname (string) – The first name of the contact.
  • lastname (string) – The last name of the contact.
  • email (string) – The contact’s email. This field will be rejected if not properly formatted.
  • phone (string) – Ten or eleven digit phone number. Format should be irrelevant.
  • extension (int) – The contact’s extension in the phone system.
  • type (bool) – What is the type of this contact (prim, scnd, auth, none)?

POST

Example usage (Note: Email parameter may be passed using either @ or %40) :

curl -k -u user:pass -d "customer=120&firstname=Jon&lastname=Lucas&email=jlucas@ringfree.com&phone=18285750030&extension=107&type=auth" https://portal.ringfree.com/api/v2/contact

Example response:

{"status":200,"message":"OK","data":"63"}

 


https://portal.ringfree.com/api/v2/contact/<id>

This route is for either retrieving or updating a contact. The contact ID naturally replacing <id> in the URL.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/contact/1

Example response:

"status":200,"message":"OK","data":{"1":{"customer":"AcctVantage ERP","customerid":"1","firstname":"Tim","lastname":"Lau","email":"tlau@acctvantage.com","phone":"18286923301","extension":"702","type":"none"}}}

 POST

Example usage:

curl -k -u user:pass -d "extension=456" https://portal.ringfree.com/api/v2/contact/63

Example response:

{"status":200,"message":"OK","data":"63"}

 


https://portal.ringfree.com/api/v2/alerts

This GET only route returns complete data for all active system alerts. POST requests to this route will return a 405 “Method not allowed” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/alerts

Example response:

{"status":200,"message":"OK","data":{"5":{"title":"Database Failover","timestamp":"2017-02-14 18:34:29","blurb":"galera-atl.ringfree.biz experienced a failover.","data":"galera-atl.ringfree.biz is presently routing to galera-nyc-01.ringfree.biz as a result of a MySQL error on galera-atl-01.ringfree.biz. Manual reversion is recommended."}}}

 


https://portal.ringfree.com/api/v2/alert

This is a POST only route for submitting new system alerts. Submitting a new alert requires three fields to be submitted in the POST data:

  • title (string): The system alert title.
  • blurb (string): The alert summary.
  • data (string): The full text of the alert.

Omitting any of the fields will result in a 400 “Bad request” error. A successful request will return the ID of the newly created alert in the data field of the response.

POST

Example usage:

curl -k -u user:pass -d "title=Testing&blurb=Alert via API&data=This is a test of the API v2 POST route to create system alerts." https://portal.ringfree.com/api/v2/alert

Example response:

{"status":200,"message":"OK","data":"6"}

 


https://portal.ringfree.com/api/v2/alert/<id>

This route is used to either retrieve data associated with an alert or to mark that alert as dismissed. In either case, replace <id> with the ID of the alert. Using the ID of an alert that does not exist will result in a 404 “Not found” error.Using a POST request to dismiss an alert that has already been dismissed will result in a 409 “Conflict” error.

Dismissing an alert requires a single entry in the POST data: a user field so that the API knows which user to identify as responsible for dismissing the alert.

Using this route with a GET request will return all data associated with the alert, including the dismissal status and, if dismissed, which user was responsible for the dismissal.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/alert/5

Example response:

{"status":200,"message":"OK","data":{"5":{"title":"Database Failover","timestamp":"2017-02-14 18:34:29","blurb":"galera-atl.ringfree.biz experienced a failover.","data":"galera-atl.ringfree.biz is presently routing to galera-nyc-01.ringfree.biz as a result of a MySQL error on galera-atl-01.ringfree.biz. Manual reversion is recommended.","dismissed":"1","dismissedby":"1"}}}

POST

Example usage:

curl -k -u user:pass -d "user=1" https://portal.ringfree.com/api/v2/alert/6

Example response:

{"status":200,"message":"OK","data":"6"}

 


https://portal.ringfree.com/api/v2/notifications/<user_id>

This is a GET only route that displays a given user’s active notifications and associated data. POST requests to this route will result in a 405 “Method not allowed” error. Replace the <user_id> with the actual user ID number.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/notifications/1

Example response:

{"status":200,"message":"OK","data":{"9":{"timestamp":"2017-02-15 08:00:00","title":"Test","blurb":"API Test","data":"This is a test of the API."}}}

 


https://portal.ringfree.com/api/v2/notification

This is a POST only route for creating new user notifications. Requests to this route must include the following data fields:

  • user (int): The ID of the user with which to assocaite the notification.
  • title (string): The notification title.
  • blurb (string): The notification summary.
  • data (string): The full text of the notification.

Omission of any of the above fields will result in a 400 “Bad request” error. GET requests to this route will result in a 405 “Method not allowed” error. Successful requests will return the ID of the newly created notification in the response data field.

POST

Example usage:

curl -k -u user:pass -d "user=1&title=API&blurb=API&data=API" https://portal.ringfree.com/api/v2/notification

Example response:

{"status":200,"message":"OK","data":"11"}

 


https://portal.ringfree.com/api/v2/notification/<id>

This route accepts GET requests as a means of retrieving data associated with a particular notification as well as POST requests as a means of dismissing a particular notification.

POST requests require a dismiss field to be included in the POST data; any truthy value will suffice. Submission of a falsey value for dismiss will result in a status 204 message. Omission of the dismiss field will result in a 400 “Bad request” error. Attempts to dismiss a notification that can not be found will result in a 404 “Not found” error. Attempts to dismiss a notification that has already been dismissed will result in a 409 “Conflict” error.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/notification/11

Example response:

{"status":200,"message":"OK","data":{"11":{"user":"1","timestamp":"2017-02-15 16:22:47","title":"API","blurb":"API","data":"API"}}}

POST

Example usage:

curl -k -u user:pass -d "dismiss=1" https://portal.ringfree.com/api/v2/notification/11

Example response:

{"status":200,"message":"OK","data":"11"}

 


https://portal.ringfree.com/api/v2/yconfpass/<ctid>

This route accepts GET requests as a means of retrieving the YConf password associated with a given PBX and POST requests as a means of setting/updating the YConf password associated with a given PBX.

POST requests require a password field to be included in the POST data; any password should suffice. Failure to provide a password will result in a 400 “Bad Request” error. GET or POST requests to a CTID that doesn’t exist will result in a 404 “Not Found” error. Successful POST requests wil return a 200 with the CTID in the data field. Successful GET requests will return either a 200 or a 204 depending on whether or not a password is currently set.

GET

Example usage:

curl -k -u user:pass https://portal.ringfree.com/api/v2/yconfpass/1900027

Example response:

{"status":200,"message":"OK","data":"examplepassword"}

POST

Example usage:

curl -k -u user:pass -d "password:newpassword" https://portal.ringfree.com/api/v2/yconfpass/1900027

Example response:

{"status":200,"message":"OK","data":"1900027"}

That’s all for now. Additional REST routes will be added in the near future.