FreePBX GraphQL Provisioning Tutorial
In this tutorial we will be writing a “new hire” script to add an extension to your FreePBX using the new GraphQL API. Our script will be running on a separate client machine where other new hire processes are carried out also, although you can run it from the PBX itself if you prefer. For the client side we’ll use an off-the-shelf CentOS VM, and we’ll be writing the script using Python (it looks much better on your resume than Perl)
References
Blog post “GraphQL support in FreePBX 15” by Jared Smith : https://www.freepbx.org/graphql-support-in-freepbx-15/
GraphQL Documentation for FreePBX: GraphQL PBX APIs Documentation
GQL Library for Python: https://github.com/graphql-python/gql
Prerequisites
PBX Side: FreePBX 15 with PBX API version 15.0.3.11 or later
Client Side : CentOS 7.9
Curl 7.29.0 (“yum install -y curl” if you don’t have it)
Python 3.6.8 (“yum install -y python3 python3-pip python3-devel” if you don’t have it)
GQL Library for Python (“pip3 install --pre gql[all]”)
PBX-side basics
To start on the PBX side, go to Connectivity -> API
We need to know the “scope” for the APIs we want to use, so go to the “Scope Visualizer” tab. The APIs we want for querying and modifying extensions are under “Core”, so click on “Read/Write for Core” and the relevant scope “gql:core” will appear in the box above the tree. Note that the tick marks here don’t change any settings, they are just generating the text “gql:core” which you can reference elsewhere.
The next thing we can do is test out the fetchAllExtensions API using the “GraphQL Explorer” (let’s you try the API without worrying about authentication). Put “gql:core” in the “Paste Scopes Here” box, and hit “Reload Explorer”, then enter this into the left-hand box under “GraphiQL”:
query {
fetchAllExtensions {
status
message
totalCount
extension {
extensionId
}
}
} |
And press the “Execute Query” button , you should get a response like this listing your extensions:
Here’s the text from my PBX (when there were only two extensions):
{
"data": {
"fetchAllExtensions": {
"status": true,
"message": "Extension's found successfully",
"totalCount": 2,
"extension": [
{
"extensionId": "4001"
},
{
"extensionId": "7006"
}
]
}
}
} |
GraphQL explanation
Hopefully that all went well. Let’s take a step back and understand how that query was constructed:
The documentation for fetchAllExtensions can be found here: Core Module GraphQL APIs - FetchallExtensions Details
Under “API request” you can see some 16 parameters (most of which can occur multiple times for this API). But GraphQL lets you request only the data you’re interested in, so the query above only asks for 4 of those. That’s because we’re just trying to get a list of existing extension IDs, to avoid a conflict when we create a new extension, the other parameters aren’t useful in this context. The query above was created by copying the full API request from the documentation and removing the parts that we don’t need.
For other purposes (for example running a weekly report listing all your extensions) you would include a lot more parameters in your request, perhaps all the parameters from the documentation would be appropriate for a detailed report.
Authentication
Now it’s time to do a real query from the GraphQL client instead of the FreePBX GUI, so we need to use authentication
We need to create an application which we will use for both of the APIs we plan to use (listing all extensions and adding a new extension). On the Applications tab, choose “Add Application” and choose “Machine-to-Machine app” from the drop-down. You can learn more about application types here: API Applications
Fill in the info about your application here. In order to do the full script we will need APIs from both the core and framework scopes, so our scope ends up being “gql:core gql:framework”, that’s what you get when you check both the Core and FreePBX Framework boxes in the Scope Visualizer:
Click “Add Application” and get the application details, make sure you copy Client Secret before hitting “Close”:
You can get all this info (except the secret) from the “eye” icon in the list of applications:
If you didn’t save the secret, you can hit the “Regenerate Credentials” button, which generates a new ID and secret while invalidating the old one.
Manual API access from client
I think it’s useful to use command line tools for the next “sanity check” step, it lets you see the exact URLs used and the actual format of the responses. But I also use “dir” to look at files on Windows, and that’s not everyone’s cup of tea. For those who would rather not deal with 1000-character long commands and hard-to-read output, you may prefer a GUI approach instead. The open source Insomnia client lets you do the same sanity check below with prettier formatting, less typing, and a lot of other features, like examining the API schema from inside the GUI. Feel free to skip the rest of this section and do the same query with Insomnia instead, it proves the same thing.
For those who like the command line, let’s test out authentication with curl. For “Client Credentials” flow (used for Machine-to-Machine authentication) we need “Token URL”, “ID”, and “Client Secret”. The curl command looks like this:
curl -X POST -u "<ID>:<Secret>" -d "grant_type=client_credentials&scope=<scope>" <Token URL>
You should get back a token like this:
Command:
Response:
That 826-character string is an access token, and we’ve got an hour to use it. Oauth2 has the concept of refreshing a token but we’ll just make sure to use it within the hour. Of course you can generate a new one if this one expires, just repeat the exact same command.
You can see the newly-granted token on the “Access Tokens” tab:
Note that the abbreviated version of the token seen here can’t be used for curl or script access, we need to use the super-sized access_token above for further access.
If the above curl command gets a timeout, make sure your PBX firewall trusts your client machine.
If the above curl command produces errors about failing to validate a certificate, you need to get HTTPS properly set up for the FreePBX GUI first. See Certificate Management User Guide and System Admin-HTTPS Setup . Another choice would be to use HTTP instead, but username and password are sent in the clear using HTTP, so this is strongly discouraged and should only be used during testing or on a trusted network.
The next part of our sanity check wins the prize for today’s longest and hardest to parse command line (also most curly braces), but should show the actual gql:core API working from our client machine :
Now the format is :
Where:
<access_token> : from “access_token” returned by previous curl command
<GraphQL Query> : We’re going to query all extensions with "query { fetchAllExtensions { status message totalCount extension { extensionId } } }"
<GraphQL URL> : from “GraphQL URL” field when we created the application (should always be “https://<pbxfqdn>:/admin/api/api/gql”)
Command:
Response:
Scripting it (wasn’t this tutorial supposed to be about scripts?)
Querying for existing extensions
With authentication and a query working in curl (or Insomnia if you prefer), we will now do the exact same two operations in Python 3. Don’t forget the prerequisites at the top of this tutorial, and of course you will need to modify the parameters for your environment. First we use the Python Requests library to do the Oauth2 authentication and get back a token, then we use the GQL library to make the GQL query and parse the returned data which has already been converted into a Python dictionary:
Here’s what it looks like in action:
Adding new extension
Now on to actually adding an extension, the first part of the script is pretty much unchanged from above, but now there’s some code to figure out the next available extension, some code to parse the command line for the user’s name, and e-mail, and finally to do two GraphQL “mutations” to actually add the extension and then apply the configuration.
Here’s the GraphQL mutation which we use to create the extension, note that outboundCid and vmPassword are generated by the script based on the new extension, while name and email are passed in as arguments:
The response from the PBX is simple for this mutation:
Applying configuration
Here’s the GraphQL mutation which we use to apply the configuration:
Here is the response from the PBX for this mutation:
Here’s what it looks like in action:
Notes
To just check existing extensions and find the next available extension, the script can be run without arguments (just “python3 addextension.py”)
If you drop the “apply” as the last argument the script will add the extension, but won’t run the “doreload” mutation to apply the configuration
The “doreload” API to apply the config is asynchronous, so when the script completes, it only indicates that the apply config operation has been started. There is another API fetchApiStatus which can be used to poll for completion (this is left as an exercise for the reader)
You may prefer to use an Oauth2 library for Python (for example oauth2-client) instead of doing the Oauth2 step with the lower-level Python Requests library. For this particular use case where the simple machine-to-machine mode is used, and we request a new token every time the script is run, the Requests library was sufficient and easy to use, but other use cases would probably benefit from a library built for this purpose