Hello! We are creating an integration to Confluence and are trying to get email addresses for users based on accountIds. We are testing our Forge app but we are unable to retrieve emails.
Using /wiki/rest/api/user/email/bulk as documented it states: Returns a user’s email address regardless of the user’s profile visibility settings. For Connect apps, this API is only available to apps approved by Atlassian, according to these guidelines. For Forge apps, this API only supports access via asApp()
We are using asApp() and have used route and assumeTrustedRoute. We get a 200 with no results. The accountIds are valid as I can use other APIs with the same accountIds and get the email back, if visibility is set to Anyone.
Just confirming, does your app currently have the read:email-address:confluence scope defined? Are you able to retrieve results via the individual request API, or does this also return empty results?
I do have the read:email-address:confluence scope in my manifest. I switched my code to use just the /email route and was able to successfully get the email returned on our account that have the Profile Visibility set to You and Admins. So that seems to work.
I switched back to the /email/bulk code, reviewed how I’m passing the query parameters for the accountIds, and think that might have been an issue based on the API documentation. I was passing as: ?accountId=xxx:uuid, yyy:uuid. That just didn’t return results; I updated to use form style ?accountid=xxx:uuid&accountId=yyy:uuid. This now returns a 401: Unauthorized Scope does not match. Which I had been getting before and now not sure again why…
Grrr. Ok, narrowed the issue with the /email/bulk endpoint.
It’s something about query parameters for multiple accountIds:
hardcode a singe value on the url -
hardcode 2 values on the url using accountId=...&accountId=..
pass a single value to the url as a parameter `?accountId=${accountIds} -
pass multiple values to the url as a parameter (formated as above) - no data returned, no errors, just no data
So something in how the query parameters are being processed when substituted in the query parm is causing the issue. I have tried to use URLSearchParams() as well to ensure encoding and such was right on the parameter, no luck.
Anybody have an example of how to get multiple values passed to the /email/bulk endpoint?
note: I have used the exact code noted here in my testing and this returns a 200 with no data.
@marc - thanks! I tested that and same result: get a 200 response, but no data. If I hardcode the query parm as:
?accountId=accid1&accountId=acc2
Works, but only when hardcoded on the route url. Can’t seem to dynamically build the query parameters. That is also following the doc I noted above, their own example, for me, doesn’t work.
route - seems to be the main issue, I think. It’s is encoding substituted query parameters and making the accountIds invalid by encoding the = in the &accountId=. If I hardcode the accountIds with the &accountId= for subsequent ids, route doesn’t encode the = and I get the expected results.
Here is the output when I try to dynamically build the accountIds query parameter using the following code:
// Separate the first accountId to be part of the static URL structure for the route tag.
const [firstId, ...subsequentIds] = accountIds;
// Create the query string for the rest of the accountIds.
const subsequentQueryString = subsequentIds.map(id => `&accountId=${id}`).join('');
// Build the final URL by combining the static part (with the first ID) and the dynamic part.
const url = route`/wiki/rest/api/user/email/bulk?accountId=${firstId}${subsequentQueryString}`;
Otherwise, according to documentation on path construction, the call might not work.
When route is called, the Forge runtime also checks for possible path manipulation attempts (for example, issueKey or repo_slug coming from the user as ../../../evil_api_call), escaping or blocking as needed.
If the URL is constructed separately, the runtime might throw an exception for this as a false positive. This is because the runtime has no way of knowing which parts might have been manipulated by the user.
Using assumeTrustedRoute
Note that assumeTrustedRoute isn’t a tagged literal but a function so we need to add parentheses.
Ok, so the tidbit about assumeTrustedRoute being a function was important, b/c with the parenthesis, the call now works with multiple accountIds in the query string in the form style:
?accountId=acc1&accountId=acc2...
Testing again for route with the same URLSearchParams does not work. Route continues to encode the the query parameters that are not handled by the API and hence no data returned.
I think this is a bug with either the route or the /email/bulk endpoint.
It would be ideal to not have to pass accountIds as query parameters. We now have to manage how many accountIds we need to pass to the endpoint.
Ultimately the endpoint should support a POST with a request body similar to other endpoints in v2 of the API (e.g. /users-bulk). This is a much cleaner approach.