Khoros Atlas Logo

Community REST API v2 - The Next Step Forward

Khoros Staff SuzieH
Khoros Staff

We here at Lithium are very excited to introduce Lithium Community REST API v2. We strived for a simplified API that has:

  •      Increased functionality and access to more resource data
  •      Ability to search and sort by a wide variety of fields of a resource
  •      Fewer calls
  •      Increased consistency

We are exposing many more resource fields than we made available in API v1. We’ve also made accessing those fields much more intuitive through Lithium Query Language (LiQL), a SQL-like query language. With a LiQL statement, you can define search criteria for a vast variety of complex queries. For example:

 

Return forum posts that have an average rating value of 4 or higher.

 

SELECT * FROM messages WHERE ratings.avg(value)>=4 AND conversation.style='forum'

 

Return the subject, ID, and conversation data for forum topics with accepted solutions.

 

SELECT subject, id, conversation FROM messages WHERE conversation.style= 'forum'  AND depth=0 AND conversation.solved=true

 

LiQL is easier to learn and use.  LiQL uses a natural language approach to filter, sort, and retrieve collections of objects like Message or User. The queries are easier to write because it lets you specify what you are looking for. Say you want to return the IDs of the four most recently posted topic messages from a specific category, authored by a specific user. Here’s your LiQL statement:

 

SELECT id FROM messages WHERE category.name = ‘recipe_board’  AND author.id = '3' AND depth = 0 ORDER BY post_time DESC LIMIT 4

 

The same call in a custom component using API v1 would look something like this:

 

rest("/users/id/3/topics/in/boards/id/recipe_board/recent?page_size=4")

 

You would have to use the users/id/[user_id] call on the Community resource, and then chain a method from the User resource to get the topics from a specific board.

 

With LiQL, you can query the community with a single STATEMENT from any node level. No more determining what calls are available for what resource type. No need for secondary code, no need to make a second call to sort the messages by date/time.

Community REST API v2 in Components and Endpoints

The rest and restadmin FreeMarker context objects have a new rest_version parameter so that you can make Community API v2 calls within your component and endpoint code.  Our Developer portal will walk you through creating your first API v2 component.

Lithium Community REST API Proxy and OAuth 2.0

When making API calls over HTTP, you will now use our API Proxy and OAuth. OAuth requires that each client application making calls to the API will need to be registered.  Lithium Support will help you register your client applications and provide you with the credentials needed to obtain Authorization grant flow tokens. OAuth 2.0 authentication grant flow describes the new API proxy and authentication service, as well as the authorization APIs.

Using Community REST API v1 with Community REST API v2

At this time, API v2 supports READ operations on the Message and User resources, as well as several supporting resources, such as Label, Image, Tag, Kudo, Rating, and more.

 

Your code can contain both v1 and v2 calls together. See When to use Community REST API v1 or v2 for details.

A custom component before and after

Let's look at a component written using API v1 and a similar component written using API v2.  Thanks to @Henrik  a member of our API v2 Beta program, for sharing this sample code with us. This v1 component retrieves the current user's last four topics from three specific boards (Starters, Main Courses, and Desserts):

 

<#if !user.anonymous>

     <#-- Assign the board IDs to a variable -->

     <#assign recipes_boards_id = ["CVS_REE_ENTREES","CVS_REE_PLATS","CVS_REE_DESSERTS"] />

     <#-- Get the topic IDs for the last four topics on each board -->

     <#assign user_recipes_id = [] />

     <#list recipes_boards_id as recipes_board_id>

          <#list rest("/users/id/${user.id?c}/topics/in/boards/id/${recipes_board_id}/recent?page_size=4").messages.message.id as user_idea_id>

               <#assign user_recipes_id = user_recipes_id + [user_idea_id?number] />

          </#list>

     </#list>

     <#-- Sort the topic IDs and keep the four most recent ones -->

     <#if user_recipes_id?size gt 0>

          <#assign user_recipes_id = user_recipes_id?sort?reverse />

          <#if user_recipes_id?size gt 4>

               <#assign user_recipes_id = [user_recipes_id[0],user_recipes_id[1],user_recipes_id[2],user_recipes_id[3]] />

          </#if>

          <#-- Call the component that builds the HTML list to display the topic IDs -->

          <@component id="CVOUS.User.Topics" title="Mes recettes" topics_id=user_recipes_id?join(",") />

     </#if>

</#if>

 

This component, rewritten with API v2, is a slightly simplified version (it retrieves topics from one board instead of three), however using a single call to the v2 API, the component can retrieve the specific messages desired from a specific user in the order needed, eliminating the need for the sort logic in the previous version of the component.  Notice that the rest_version parameter (2.0) is passed to the rest FreeMarker method. This is a change from API v1, where REST calls made via FreeMarker do not require the rest_version parameter. (Your existing API v1 rest and restadmin calls will continue to work as expected -- a rest or restadmin FreeMarker call written with API v2 defaults to v1 if rest_version is not provided.)

 

<#if !user.anonymous>

     <#-- Get the four most recent topic IDs of topics from the recipe category written by the current user -->

     <#assign user_recipes = rest("2.0","/search?q=" + "SELECT id FROM messages WHERE category.name IN ('CVS_REE_RECETTES') AND author.id = '${user.id?c}' AND depth = 0 ORDER BY post_time DESC LIMIT 4"?url).data.items />

     <#assign user_recipes_id = [] />

     <#list user_recipes as user_recipe><#assign user_recipes_id = user_recipes_id  + [user_recipe.id] /></#list>

     <#-- Call the component that builds the HTML list to display the topic IDs -->

     <#if user_recipes?size gt 0>

          <@component id="CVOUS.User.Topics" title="Mes recettes" topics_id=user_recipes_id?join(",") />

     </#if>

</#if>

More examples

Let’s look at an example from @nathan  one of our Lithium Stars. This example uses Community REST API v2 to get a list of all idea statuses along with the count of how many ideas have each status. This particular code sample is designed for use in and endpoint, and returns the idea statuses and counts in JSON.

 

The LiQL query in bold retrieves the status field on messages that match the constraints in the WHERE clause. The query filters the Message collection by conversation style (what you knew as interaction style or discussion style in API v1) and by topic messages (signified by depth=0). The result set is limited to 300.

 

After confirming a successful request, the example uses fields from the message.status subproperty on the Message resource to retrieve the statuses and counts.

 

<#include "objecttojson" />

<#setting url_escaping_charset='ISO-8859-1'>

<#assign query = http.request.parameters.name.get("q", "select status from messages where conversation.style=\"idea\" and depth=0 limit 300") />

<#assign result = rest("2.0","/search?q=" + query?url) />

<@compress>

{

<#if result.status = "success">

     "result": "success",

     <#assign statusKeys = {} />

     <#list result.data.items as message>

          <#if message.status??>

               <#if !statusKeys?keys?seq_contains(message.status.key)>

                    <#assign statusKeys = statusKeys + {message.status.key: 1} />

               <#else>

                    <#assign statusKeys = statusKeys + {message.status.key: (statusKeys[message.status.key] + 1)} />

               </#if>

          </#if>

     </#list>

     "ideaKeys": ${objectToJsonFunction(statusKeys)}

<#else>

"result": "error"

</#if>

}

</@compress>

 

@Henrik was kind enough to contribute a second component example using Community REST API v2. This example gives the last 500 comments (or less) for a specific topic.

 

<h1>Comments' Extract</h1>

<#-- Pull the topic ID for the current message in session  -->

<#assign topic_id = http.request.parameters.name.get("topic_id", "XXX") />

<#if topic_id != "XXX">

<#-- use depth > 0 to retrieve comments on the topic message. Topic messages have a depth of 0. First level comments have a depth of 1, and so on -->

<#assign replies = rest("2.0","/search?q=" + "SELECT id, body, author.login, post_time FROM messages WHERE topic.id = '${topic_id}' AND depth > 0 ORDER BY post_time DESC LIMIT 500"?url).data.items />

<table>

     <tr>

          <td>id</td>

          <td>body</td>

          <td>author.login</td>

          <td>post_time</td>

     </tr>

     <#list replies as reply>

     <tr>

          <td>${reply.id}</td>

          <td>${reply.body}</td>

          <td>${reply.author.login}</td>

          <td>${reply.post_time?string("dd/MM/yyyy")}</td>

     </tr>

     </#list>

</table>

<#else>

 

Add ?topic_id=XXX to your navigator URL where XXX is the topic id of the comments you want. For instance :

 

<p>www.cvous.com/t5/Exports-des-commentaires/ct-p/EXPORTCOMMENTS<span style="color: #F09;">?topic_id=60751

 

</#if>

Where Can I Learn More?

Come look at our new Community API v2 knowledge base on the Developer Network. Begin with Using LiQL, then learn more about the response, error responses, application error codes, and OAuth2.

 

Finally, each new resource, such as the Message resource, has an individual knowledge base article with property descriptions, a list of properties available for use in LiQL constraints, and example queries.

 

As always, we welcome feedback and contributions. As you get started with Community REST API v2, we encourage you to share samples and let the rest of the developer community see the innovative features and tools you create. And we’d love to hear your suggestions for future tech blog posts - let us know if you’re interested in contributing one!

 

10 Comments
Occasional Advisor
Occasional Advisor
 
Frequent Advisor
Frequent Advisor

I am getting an error with the following call:

SELECT subject, id, conversation FROM messages WHERE conversation.style= 'forum'  AND depth=0 AND conversation.solved='true'

 

Error: 

{
"status" : "error",
"message" : "Validation exception for conversation.solved in WHERE clause: conversation.solved = true",
"data" : {
"type" : "error_data",
"code" : 604,
"developer_message" : "Call not supported: problem with constraint. Check the type of the value or the operator",
"more_info" : ""
},
"metadata" : { }
}

 

Any ideas?

Frequent Advisor
Frequent Advisor

Changing conversation.solved='true' to a boolean worked!

conversation.solved=true

 

Suggest you add this to your main post.

 

Thanks.

Khoros Staff SuzieH
Khoros Staff

Thanks, @darrenSP. We've fixed that error in the example.

Honored Contributor Honored Contributor
Honored Contributor

Is there any possibility to get node settings (default and custom ones) with the v2 API? Basically looking for something to replace

${rest("/category/id/<category.id>/settings/name/<setting.key>").value

any advice?

Honored Contributor Honored Contributor
Honored Contributor

Is there any possibility to get node settings (default and custom ones) with the v2 API? Basically looking for something to replace

${rest("/category/id/<category.id>/settings/name/<setting.key>").value

any advice?

Khoros Staff SuzieH
Khoros Staff

Hi @luk,

We don't have a public v2 equivalent at this time. We actually do have it internally, but it requires some refactoring and isn't publically available yet.

Honored Contributor Honored Contributor
Honored Contributor

Thanks for the clarification @SuzieH, any estimate when we could see this released? 

Honored Contributor Honored Contributor
Honored Contributor

@SuzieH: There are some other things with v2 (or v1 regardless) that are missing (where to report?):

 

- Board nodes have (recently?) received a new property field "description_long" which seems to allow HTML (which "description" does not), but there seems to be no way to get this field's content via API, this is what happens:

{
  "status" : "error",
  "message" : "invalid query syntax for SELECT description_long FROM boards",
  "data" : {
    "type" : "error_data",
    "code" : 604,
    "developer_message" : "Field 'description_long' does not exist for object 'boards'. in: SELECT description_long FROM boards",
    "more_info" : ""
  },
  "metadata" : { }
}

you can find this when opening the "Properties" modal-window in the community structure (where you edit title/short_title etc.)

 

- Threads/Topics have recently received the long needed possibility to add a cover image (all conversation styles), now I would expect to get this returned by a call to messages, like:

SELECT * FROM messages WHERE depth = 0

which we do not, as far as i can tell. there is of course the query for images that is returned by the above:

SELECT * FROM images WHERE messages.id = '<id>'

which then returns the cover image among all other images in that message...the problem is, how do we know which image is the cover image, as all image-items returned will look exactly the same? The "rule" seems like "whichever image is added first is also the first item in the API response"... therefore there is no reliable way of getting the cover image and thus to use this great new feature accordingly. Any plans to improve this?

 

EDIT: Found the solution regarding the cover image, it has to be explicitly added to fields in the statement, according to the docs "The cover image set for the message. Must be called in the SELECT statement explicitly to be returned. Is not returned if a cover image is not selected for a message."

 

EDIT2: Problem with this is that if I try something like this:

SELECT *, cover_image FROM messages WHERE id = '<id>' 

an error is thrown, therefore I would have to specify EACH field explicitly or make a second request for only the cover_image...both are not optimal solutions. Any better ideas?

Khoros Staff SuzieH
Khoros Staff

Hi @luk

You asked where to post/report issues/questions like these.  Probably the best place would be the Developer Discussion forum. It's not quite ideal, but you'll get more eyes on the post. Now that our API offering has expanded to Responsive, Community, SMM, and more, it might be time to have more forums for more focused discussions. Always feel free to @mention me. If it's a straight-up Doc question, you can also email documentation@lithium.com.  

For the first question about long_description, it looks like you'll need v1 /boards/id/<board id>/settings/board.description. I'm not sure when we added the long description field. Here are the setting names for the different discussion styles. I need to check to see if we have an enhancement to add this field to v2. 

  • Forums: board.description_long
  • Blogs: blog.description_long
  • Ideas: idea.description_long
  • Contest: contest.description_long
  • QandA: qanda.description_long
  • TKB: tkb.description_long

For the second issue, do you need to get all fields for the message object? (Meaning, do you need to do select *?) Generally, we recommend narrowing the LiQL query to improve performance. I was able to return cover image successfully with this query:

select board, subject, body, view_href, cover_image from messages where id = "432"

 

Finally, I don't have any estimated timeframe for when you'll be able to get node settings via v2.

 

Let me know if you need anything more or if I've misunderstood your questions.