- Joined
- Jul 9, 2024
- Messages
- 14
- Reaction score
- 10
In the last few weeks I have learned a lot from this community, and as a way to give back I wanted to give some concise, hopefully invaluable points to help with the understanding of how things work.
Escape characters
First off, we have escape characters. Typically in an API call, you might use the + and & and ' symbols in request URLs. You will need to escape these.
'+' is %2B
' is %27
& is &
= is %3D
If you don't do this, then when you send the URL request containing the unescaped characters it will likely error (this is due to REST calls typically being sent to a SQL database backend, and SQL will treat the unescaped characters like commands rather than search criteria).
One call to find contact ID, another call to retrieve that contact's details
Another issue I ran into was finding the CRM template generator had a limitation of its wizard, where it only worked for APIs where the name details were returned from a number search.
In a lot of CRMs, the name details are returned from a separate call. For example:
The result might only return a 'contactid' but no name, so you would need to do a subsequent search to get the name.
The trick here is to have this near the end of the scenario:
<Outputs Next="[NextScenarioName]" AllowEmpty="false" /> //After finishing this scenario, it will run the '[NextScenarioName]' scenario next. The 'AllowEmpty'=False means it won't run the next scenario if it didn't get a result from the scenario that just finished running.
An example generic script
Comments on the above example
Data paths follow the data.object convention. However, if an object is nested in multiple parent objects, you can omit the 'data' root. For example, if you had data.telNo.numberOwner.personName.legalFirstName then you could just have telNo.numberOwner.personName.legalFirstName as the path instead (3CX will be able to work out that 'data' is implied at the beginning)..
This will not work if you are only doing a single-level result however, so putting 'FirstName' instead of 'data.FirstName' would just give an error.
Arrays
If for any reason, you are working with a result that returns as an array, the trick to deal with it is (many thanks to @edossantos_sipcaller who guided me on this):
In other words, if an array is returned then you need to filter it by a different variable from the one you want to take as an output.
See example:
I honestly still don't fully understand this particular concept so someone else may be able to explain better, but you can always take that snippet of code to experiment with and maybe you can get a better understanding that way.
Escape characters
First off, we have escape characters. Typically in an API call, you might use the + and & and ' symbols in request URLs. You will need to escape these.
'+' is %2B
' is %27
& is &
= is %3D
If you don't do this, then when you send the URL request containing the unescaped characters it will likely error (this is due to REST calls typically being sent to a SQL database backend, and SQL will treat the unescaped characters like commands rather than search criteria).
One call to find contact ID, another call to retrieve that contact's details
Another issue I ran into was finding the CRM template generator had a limitation of its wizard, where it only worked for APIs where the name details were returned from a number search.
In a lot of CRMs, the name details are returned from a separate call. For example:
Code:
https://api.example.com/telephones?appid=123&appsecret=456&filter=telNo%3D%27[Number]%27
The result might only return a 'contactid' but no name, so you would need to do a subsequent search to get the name.
The trick here is to have this near the end of the scenario:
<Outputs Next="[NextScenarioName]" AllowEmpty="false" /> //After finishing this scenario, it will run the '[NextScenarioName]' scenario next. The 'AllowEmpty'=False means it won't run the next scenario if it didn't get a result from the scenario that just finished running.
An example generic script
XML:
<?xml version="1.0"?>
<Crm xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Country="US" Name="Example" Version="1" SupportsEmojis="true">
<Number Prefix="AsIs" MaxLength="-1" /> //The 'MaxLength' being set as '-1' means there is no limit to the length of the number it will search. If this was '5' for example, it would only search for the final 5 digits of the number, cutting off anything to the left.
<Connection MaxConcurrentRequests="2" /> //Means it cannot do more than 2 API call lookups simultaneously.
<Parameters />
<Authentication Type="No" /> //This is because the authentication is basic (included in the URL)
//If authentication was more advanced, you would need to amend this. For example:
<Authentication Type="Basic">
<Value>username:password</Value>
</Authentication>
<Scenarios> //There are two scenarios- the first gets the contact ID, the second uses the contact ID to get the firstname and lastname.
<Scenario Id="" Type="REST" EntityId="Contacts" EntityOrder=""> //This is the first scenario.
<Request SkipIf="" Url="https://api.example.com/telephones?appid=123&appsecret=456&filter=telNo%3D%27[Number]%27" MessagePasses="0" RequestEncoding="UrlEncoded" RequestType="Get" ResponseType="Json" />
<Rules>
<Rule Type="Any">data.contactid</Rule> //The rule should always have 'data.[variable]' within, like this. It needs to be a path.
</Rules>
<Variables>
<Variable Name="ContactId" LookupValue="" Path="">data.contactid<Filter /></Variable> //This created a variable called 'ContactId' and populates it with the value returned from 'data.contactid' from the API call.
</Variables>
<Outputs Next="GetContactById" AllowEmpty="false" /> //After finishing this scenario, it will run the 'GetContactById' scenario next.
</Scenario>
<Scenario Id="GetContactById" Type="REST" EntityId="" EntityOrder=""> //This is the 'GetContactById' scenario.
<Request SkipIf="" Url="https://api.example.com/telephones?appid=123&appsecret=456&filter=contactid%3D%27[ContactId]%27" MessagePasses="0" RequestEncoding="UrlEncoded" RequestType="Get" ResponseType="Json" />
//The 'SkipIf' means it won't run this scenario if the 'ContactId' variable is empty. If the variable is empty, it indicates no matching contact was found against the calling number.
<Rules>
<Rule Type="Any">data.contactid</Rule> //Again, this rule checks for the existence of this variable being returned. If this doesn't return, the scenario as a whole will fail.
</Rules>
<Variables>
<Variable Name="ContactId" Path="">data.contactid<Filter /></Variable>
<Variable Name="FirstName" Path="">data.firstName<Filter /></Variable>
<Variable Name="LastName" Path="">data.lastName<Filter /></Variable>
</Variables> //The above three are fairly self-explanatory.
<Outputs AllowEmpty="false">
<Output Type="ContactID" Passes="0" Value="[ContactId]" />
<Output Type="ContactUrl" Passes="0" Value="[ContactId]" />
<Output Type="FirstName" Passes="0" Value="[FirstName]" />
<Output Type="LastName" Passes="0" Value="[LastName]" />
<Output Type="EntityId" Passes="0" Value="[ContactId]" />
<Output Type="PhoneBusiness" Value="[Number]"/>
<Output Type="EntityType" Passes="0" Value="Contacts" />
</Outputs>
//These 'output' types are mandatory for 3CX in order to return a successful response. If you don't need to use them for any reason, they still need to be included so the scenario doesn't fail.
//Anything in [these] is considered a variable. Therefore, Value="[cheeseburger]" would be whatever the value in the variable 'cheeseburger' would be.
//The value of 'cheeseburger' would be set in the 'variables' section like you see values are set for 'ContactId', 'FirstName' and 'LastName'.
</Scenario>
</Scenarios>
</Crm>
Comments on the above example
Data paths follow the data.object convention. However, if an object is nested in multiple parent objects, you can omit the 'data' root. For example, if you had data.telNo.numberOwner.personName.legalFirstName then you could just have telNo.numberOwner.personName.legalFirstName as the path instead (3CX will be able to work out that 'data' is implied at the beginning)..
This will not work if you are only doing a single-level result however, so putting 'FirstName' instead of 'data.FirstName' would just give an error.
Arrays
If for any reason, you are working with a result that returns as an array, the trick to deal with it is (many thanks to @edossantos_sipcaller who guided me on this):
If you want to get the ID from that JSON response, you could use for example this:
XML:<Variable Name="TelephoneId" Path="telephoneNumbers.id"> <Filter> <Rule Type="Equals" Ethalon="TelephoneNumber">telephoneNumbers.entityType</Rule> </Filter> </Variable>
That will get the value from the id node when the entityType node has the value "TelephoneNumber". However, if more than 1 children items met this condition, the matching will fail. It must be unique. And from what you sent as a sample response, that entityType is repeated in many sub items.
In other words, if an array is returned then you need to filter it by a different variable from the one you want to take as an output.
See example:
XML:
<Rules>
<Rule Type="Any">telNos.id</Rule> //Want to take the 'id' value as the output from the scenario.
</Rules>
<Variables>
<Variable Name="ContactId" Path="telNos.id"><Filter>
<Rule Type="Equals" Ethalon="TelNo">telNos.entityType</Rule> //Set the rule to filter by any 'entityType' where the value matched 'TelNo'.
</Filter>
</Variable>
</Variables>
Attachments
Last edited: