How to render user links via Confluence's "Convert content body" REST API?

The Convert Content Body API works as described in Bob’s answer. In our case we were pulling content from the Task Search REST API which is returning storage format that the REST API can’t render. A follow-up thread for this issue can be found here.

Hey there,

I’m trying to use the Convert content body REST API to render short snippets of storage format. These snippets can contain user links.

Raw example (replaced accountId with “123”):

<span><ac:link><ri:user ri:userkey="123" /></ac:link> b la balfs <ac:link><ri:user ri:userkey="123" /></ac:link>  ablfjas</span>

Other content is converted correctly, but the user links never get resolved:

<span><a class="unresolved" href="#" title="This user does not have access to this site">Unlicensed user</a> b la balfs <a class="unresolved" href="#" title="This user does not have access to this site">Unlicensed user</a>  ablfjas</span>

I’ve tried using the formats view, styled_view and export_view. I’ve tried these options both while being authenticated as the app, as well as with user impersonation. I’ve also tried with and without contentIdContext.

If this doesn’t work, there is an obvious workaround: I can probably just parse the storage format for ri:user elements myself and then render my own HTML for the given accountId - but getting it directly from the API would of course be much nicer.

Is anyone able to do this correctly? I have the feeling that this might be one of those GDPR features

1 Like

It is working for us – can you send an example of the rest request you are making? Ours is pretty simple, and is using user impersonation:

      method: 'post',
        '/contentbody/convert/view?spaceKeyContext=' +
        encodeURIComponent(spaceKey) +
        '&pageIdContext=' +
      body: {
        representation: 'storage',
        value: '<ac:link><ri:user ri:userkey="a-valid-account-id" /></ac:link>'
<a class="confluence-userlink user-mention current-user-mention" data-username="a-valid-acount-id" data-account-id="a-valid-acount-id" href="" target="_blank" data-linked-resource-id="98074626" data-linked-resource-version="1" data-linked-resource-type="userinfo" data-base-url="">John Doe</a>

Granted, there’s a bunch of other stuff that doesn’t work real well with that API, but the user links seem to be coming out ok at least.


Thanks Bob, that’s very helpful! This shows me that it must be something on my end. I also see you are using the spaceKeyContext which I didn’t try yet (but shouldn’t matter according to the docs when you supply a contentIdContext) and I also just realized that I only ever tried this with a single accountId. So there’s still some variables that can be tweaked here.

I’ll report back when I had the chance to play with it more! Thanks! :slight_smile:


So, since I thought I was going crazy I let one of my colleagues also try this…

  • We tried with different users.
  • We tried with a spaceKeyContext.
  • We tried with a pageIdContext instead of a contentIdContext.
  • We tried it on different Confluence instances (2 dev instances and an actual paid one).
  • We tried using User Impersonation and Addon Authentication in Spring Boot Connect. (We also tried adding the "ADMIN" scope)
  • We tried manually sending the requests with an Auth Token in Postman.

But no luck. :frowning:

To keep it simple and easy to reproduce I’m going to share what we tried in Postman instead of the Java code. We also added a cheese macro to check whether the conversion works at all.

This is the URL we sent a POST request (authenticated with Auth Token) against:


This is the POST body:

    "value":"<ac:structured-macro ac:name=\"cheese\" /> <span class=\"placeholder-inline-tasks\">Todo 6 <ac:link><ri:user ri:userkey=\"YOUR_USER_ID\" /></ac:link><time datetime=\"2020-03-17\" /></span>"

This is what we’re getting back:

    "value": "I like cheese! <span class=\"placeholder-inline-tasks\">Todo 6 <a class=\"unresolved\" href=\"#\" title=\"This user does not have access to this site\">Unlicensed user</a><time datetime=\"2020-03-17\" class=\"date-upcoming\">17.03.2020</time></span>",
    "representation": "view",
    "_expandable": {
        "webresource": "",
        "embeddedContent": "",
        "mediaToken": ""
    "_links": {
        "base": "https://<instance>",
        "context": "/wiki"

The “This user does not have access to this site / Unlicensed user” message doesn’t make sense to us at all, because:

  • The accountId in the ri:userkey attribute is the userkey of the person making the request.
  • The same user also created the page of which we passed the contentId (and has access to it)

Because of “Unlicensed user” we thought that it may have to do with the DEV instance and we therefore also tried with a paid account on a paid Confluence instance. Doesn’t work either.

We also tried copying the exact request of Bob (of course with our own space, page and user IDs) but that also doesn’t work. No idea what you’re doing differently to @BobBergman at this point… But I guess we’re officially stuck now. Help! :see_no_evil:

If you want to share your sample cheese macro source and/or descriptor url I could try running it against our dev instance to see if there is any obvious difference.

1 Like

The Cheese macro comes with Confluence by default, you should be able to try it as is. Thanks Bob! :slight_smile:

1 Like

We figured out where the problem is!

The content we tried to render was obtained via the Task Search REST API. Probably should have mentioned this from the start. This API seems to return invalid accountIds in userlinks that are inside the task.body!

We confirmed this by looking at the storage format of the page that the tasks are in where the values are different.

Thanks for helping @BobBergman !

(Of course our problem isn’t solved but the question I asked in this thread was how to render user links. Since you answered that question I’ll mark it as a solution :slight_smile:)

1 Like

Glad you managed to get better information finally, at least!

We were able to find more information on this topic and there is also a similar existing issue.

So, if we understand correctly, the problem seems to be that the Task Search API returns:

  <ri:user ri:userKey="<accountId>" />

This of course doesn’t work because an accountId is not a valid userKey. According to the linked issue, if you have an accountId you need to do the following:

  <ri:user ri:accountId="<accountId>" ri:username="<accountId>" />

So, we decided to parse the storage format that we are getting from the REST API and adjust it ourselves. This is the code we use for the workaround:

final Document doc = Jsoup.parse(task.getBody(), "", Parser.xmlParser());
final Elements userLinks ="ri|user");

for (final Element userLink : userLinks) {
	final Attributes attr = userLink.attributes();
	if (attr.hasKey("ri:userkey")) {
		final String accountId = attr.getIgnoreCase("ri:userkey");
		attr.put("ri:accountId", accountId);
		attr.put("ri:username", accountId);
	} else {
		log.warn("No ri:userkey found - this could mean that Atlassian has fixed the Task Search REST API.");


We are in contact with Atlassian and it looks like this might be fixed soon. :slight_smile:

1 Like