Persistent 401 Unauthorized calling /rest/api/3/user/{accountId}/groups from Forge (api.asUser())

Hi Community,

I’m developing a Forge app for Jira Cloud that aims to display the groups a specific user belongs to. I’m using the /rest/api/3/user/{accountId}/groups endpoint for this purpose, making the API call using api.asUser().

I’m encountering a persistent 401 Unauthorized error when calling this specific endpoint, and I’m running out of ideas on how to solve it.

Here are the relevant parts of my setup and the error logs:

App Goal: Display groups for a given accountId. Initially hardcoded, but now dynamically fetched using /rest/api/3/myself.

API Endpoint Causing Issue: GET /rest/api/3/user/{accountId}/groups

API Call Method: api.asUser().requestJira(...)

Manifest Scopes: My manifest.yml includes the necessary scopes as per the API documentation:

permissions:
  scopes:
    - read:jira-user
    - read:group:jira
    # Other scopes are present for other features but these are the relevant ones
    # - manage:jira-configuration
    # - read:jira-work
    # - read:application-role:jira
    # - read:user:jira
    # - read:avatar:jira

(Note: I have also tried with only read:jira-user or only read:group:jira with no difference).

Backend Resolver (getUserGroups): This is the resolver making the failing call:

resolver.define('getUserGroups', async (req) => {
    const { accountId } = req.payload;
    console.log(`[getUserGroups] Resolver called for accountId: ${accountId}`);

    if (!accountId) {
        console.error('[getUserGroups] accountId is required but missing in payload.');
        throw new Error('accountId is required');
    }

    try {
        let startAt = 0;
        const maxResults = 50;
        let isLast = false;
        const userGroups = [];

        console.log(`[getUserGroups] Starting pagination to fetch groups for ${accountId}`);

        while (!isLast) {
            console.log(`[getUserGroups] Fetching page with startAt=${startAt} and maxResults=${maxResults}`);

            const groupsResponse = await api.asUser().requestJira( // <-- api.asUser() call
                route`/rest/api/3/user/${accountId}/groups?startAt=${startAt}&maxResults=${maxResults}`,
                {
                    headers: {
                        'Accept': 'application/json'
                    }
                }
            );

            console.log(`[getUserGroups] API response status: ${groupsResponse.status} ${groupsResponse.statusText}`); // <-- Logs 401 Unauthorized

            if (!groupsResponse.ok) {
                 // Error handling based on status...
                throw new Error(`Failed to fetch user's groups: ${groupsResponse.statusText}. Status: ${groupsResponse.status}`);
            }

            // ... processing data (never reached due to 401)
            isLast = groupsData.isLast;
            startAt += maxResults;
        }

        console.log(`[getUserGroups] Pagination finished. Found ${userGroups.length} groups for user ${accountId}`);
        return { groups: userGroups };

    } catch (error) {
        console.error(`[getUserGroups] An error occurred during group fetch for ${accountId}:`, error);
        throw error;
    }
});

Key Observation: /rest/api/3/myself Works!

I also have a resolver (getCurrentUserAccountId) that calls /rest/api/3/myself using api.asUser(). This call succeeds with a 200 OK and returns the current user’s accountId and displayName.

// ... in the same index.js file

resolver.define('getCurrentUserAccountId', async () => {
    console.log('[getCurrentUserAccountId] Resolver called.');
    try {
        const response = await api.asUser().requestJira(route`/rest/api/3/myself`, { // <-- api.asUser() call
            headers: {
                'Accept': 'application/json'
            }
        });
        console.log(`[getCurrentUserAccountId] API response status: ${response.status} ${response.statusText}`); // <-- Logs 200 OK
        if (!response.ok) {
            throw new Error(`Failed to fetch current user information: ${response.statusText}`);
        }
        const userData = await response.json();
        console.log('[getCurrentUserAccountId] Fetched user data:', { accountId: userData.accountId, displayName: userData.displayName });
        return { accountId: userData.accountId, displayName: userData.displayName };
    } catch (error) {
        console.error('[getCurrentUserAccountId] An error occurred:', error);
        throw error;
    }
});

The frontend successfully calls getCurrentUserAccountId, gets the user’s ID (which is 712020:ef130e54-ba84-43ab-b516-31a58a31693a in my tests), and then calls getUserGroups with this ID. The getUserGroups resolver then gets the 401.

Error Logs: Here are the logs showing the successful /myself call followed by the failed /user/groups call:

invocation: [ID_1 - getCurrentUserAccountId invocation ID] index.handler
INFO     [TIMESTAMP_1]  [getCurrentUserAccountId] Resolver called.
INFO     [TIMESTAMP_1]  [getCurrentUserAccountId] API response status: 200 OK
INFO     [TIMESTAMP_1]  [getCurrentUserAccountId] Fetched user data: {
  accountId: '712020:ef130e54-ba84-43ab-b516-31a58a31693a',
  displayName: 'Romera, Jordi (ES)'
}
INFO     [TIMESTAMP_1]  [getCurrentUserAccountId] Returning accountId: 712020:ef130e54-ba84-43ab-b516-31a58a31693a

invocation: [ID_2 - getUserGroups invocation ID] index.handler
INFO     [TIMESTAMP_2]  [getUserGroups] Resolver called for accountId: 712020:ef130e54-ba84-43ab-b516-31a58a31693a
INFO     [TIMESTAMP_2]  [getUserGroups] Starting pagination to fetch groups for 712020:ef130e54-ba84-43ab-b516-31a58a31693a
INFO     [TIMESTAMP_2]  [getUserGroups] Fetching page with startAt=0 and maxResults=50
INFO     [TIMESTAMP_2]  [getUserGroups] API response status: 401 Unauthorized // <-- Problem here
ERROR    [TIMESTAMP_2]  [getUserGroups] Error fetching groups page: 401 Unauthorized
ERROR    [TIMESTAMP_2]  [getUserGroups] An error occurred during group fetch for 712020:ef130e54-ba84-43ab-b516-31a58a31693a: Error: Failed to fetch user's groups: Unauthorized. Status: 401
... (stack trace)

(Note: Timestamps and invocation IDs will vary)

Troubleshooting Steps Taken:

  1. Confirmed necessary scopes (read:jira-user, read:group:jira) are in manifest.yml.
  2. Deployed and reinstalled the app multiple times.
  3. Verified that the accountId being passed to getUserGroups is correct (it’s the same ID returned by /myself).
  4. Checked that the user running the app has the “Browse users and groups” global permission in Jira.
  5. Checked the Atlassian Status page (no relevant incidents reported at the time).
  6. Tried testing with a different user (a Jira Admin), and the issue persists.

The fact that api.asUser() works for /myself but fails with a 401 for /user/{accountId}/groups on the same site, for the same user, within moments of each other, is very puzzling. It doesn’t seem to be a general authentication issue with api.asUser() or a simple missing scope/permission.

Has anyone encountered a similar issue with this specific endpoint or a pattern where api.asUser() works for some endpoints but not others with a 401? Are there any known site configurations or edge cases that could cause this?

Any help or suggestions would be greatly appreciated!

Thanks,
Jordi

Hi Jordi,

Thanks for providing lots of info with this issue you are having.

Best bet is to raise an ticket with us, so we can get more detailed info that we cannot share here regarding your app, trace ID’s and any specific customer instances it is occurring on.

You can raise it here: Jira Service Management

Cheers
Ben

Hello @Benny

I already opened a ticket in the Atlassian Ecosystem: Jira Service Management

Regards,
Jordi

1 Like

Thanks Jordi, hopefully we can resolve this quickly for you.

It’s solved thank you. :slight_smile:

1 Like