Groups API

Programmatically create and manage groups for multi-tenant access control and organization management.

Overview

The Groups API allows you to create and manage groups within your OAuth42 application. Groups are essential for multi-tenant SaaS applications, enabling you to:

  • Organize users by tenant, organization, or team
  • Control access to resources based on group membership
  • Pass group information to federated applications via /oauth2/userinfo
  • Implement role-based access control (RBAC) with group naming conventions

Multi-Tenant Use Case

Use group names to represent tenant access and permissions:

  • acme_corp_admin - Admin access to Acme Corp tenant
  • acme_corp_read - Read-only access to Acme Corp
  • globex_admin - Admin access to Globex tenant

When users authenticate, their group memberships appear in the /oauth2/userinfo response, allowing your application to determine which tenants they can access.

Authentication & Scopes

All Groups API endpoints require authentication using a valid access token:

Authorization: Bearer YOUR_ACCESS_TOKEN

Groups are scoped to the authenticated customer. Each customer can only access and manage their own groups.

OAuth2 Scopes for Client Credentials

When using client_credentials flow (service accounts or OAuth applications), different operations require different scopes:

OperationMethodRequired Scope
List groupsGET /groupsread or admin
Get group detailsGET /groups/{id}read or admin
Get group membersGET /groups/{id}/membersread or admin
Create groupPOST /groupswrite or admin
Update groupPUT /groups/{id}write or admin
Add group memberPOST /groups/{id}/members/{user_id}write or admin
Delete groupDELETE /groups/{id}admin only
Remove group memberDELETE /groups/{id}/members/{user_id}admin only

🟢 read scope

Read-only access. Use for analytics services, monitoring tools, and dashboards that only need to view group data.

🟡 write scope

Create and update access (includes read permissions). Use for services that need to manage groups and members but don't need delete permissions.

🟠 admin scope

Full CRUD access (includes read and write permissions). Required for delete operations. Use for administrative tools and full management services.

💡 User Tokens

User access tokens (from authorization_code flow) do not require specific scopes for these endpoints. Users have implicit access to manage groups within their own customer account.

Example: Service Account with Read Scope

// Get token with read scope
const tokenResponse = await fetch('https://api.oauth42.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'sa_your_service_account_id',
    client_secret: 'your_secret',
    scope: 'read'  // Read-only access
  })
});

const { access_token } = await tokenResponse.json();

// Now you can list groups
const groups = await fetch('https://api.oauth42.com/groups', {
  headers: { 'Authorization': `Bearer ${access_token}` }
}).then(r => r.json());

// ✅ This works - read scope allows GET
console.log(groups);

// ❌ This would fail - read scope does NOT allow POST
// await fetch('https://api.oauth42.com/groups', {
//   method: 'POST',
//   headers: { 'Authorization': `Bearer ${access_token}` },
//   body: JSON.stringify({ name: 'new_group' })
// }); // Returns 403 Forbidden

Create Group

POST/groups

Create a new group within your customer account.

Request Body

{
  "name": "acme_corp_admin",
  "description": "Administrators for Acme Corp tenant",
  "organization_id": "optional-org-uuid",
  "member_ids": ["user-uuid-1", "user-uuid-2"]
}
namerequired

Unique group name. Must be unique within your customer account.

descriptionoptional

Human-readable description of the group's purpose.

organization_idoptional

UUID of organization to scope this group to.

member_idsoptional

Array of user UUIDs to add as initial members.

Success Response (201 Created)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "acme_corp_admin",
  "description": "Administrators for Acme Corp tenant",
  "customer_id": "customer-uuid",
  "organization_id": null,
  "created_at": "2025-12-24T18:30:00Z",
  "updated_at": "2025-12-24T18:30:00Z"
}

Example Request

const response = await fetch('https://api.oauth42.com/groups', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'acme_corp_admin',
    description: 'Administrators for Acme Corp tenant'
  })
});

const group = await response.json();
console.log('Created group:', group.id);

List Groups

GET/groups

List all groups for the authenticated customer.

Success Response (200 OK)

{
  "groups": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "acme_corp_admin",
      "description": "Administrators for Acme Corp tenant",
      "customer_id": "customer-uuid",
      "member_count": 3,
      "created_at": "2025-12-24T18:30:00Z"
    },
    {
      "id": "660e8400-e29b-41d4-a716-446655440001",
      "name": "globex_read",
      "description": "Read-only access to Globex",
      "customer_id": "customer-uuid",
      "member_count": 5,
      "created_at": "2025-12-24T19:00:00Z"
    }
  ],
  "total": 2
}

Example Request

const response = await fetch('https://api.oauth42.com/groups', {
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  }
});

const data = await response.json();
console.log(`Found ${data.total} groups`);

Get Group

GET/groups/{group_id}

Get detailed information about a specific group, including its members.

Success Response (200 OK)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "acme_corp_admin",
  "description": "Administrators for Acme Corp tenant",
  "customer_id": "customer-uuid",
  "organization_id": null,
  "members": [
    {
      "user_id": "user-uuid-1",
      "email": "[email protected]",
      "name": "John Doe",
      "joined_at": "2025-12-24T18:30:00Z"
    },
    {
      "user_id": "user-uuid-2",
      "email": "[email protected]",
      "name": "Jane Smith",
      "joined_at": "2025-12-24T18:35:00Z"
    }
  ],
  "created_at": "2025-12-24T18:30:00Z",
  "updated_at": "2025-12-24T18:35:00Z"
}

Update Group

PUT/groups/{group_id}

Update a group's name or description.

Request Body

{
  "name": "acme_corp_superadmin",
  "description": "Super administrators for Acme Corp"
}

Success Response (200 OK)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "acme_corp_superadmin",
  "description": "Super administrators for Acme Corp",
  "customer_id": "customer-uuid",
  "updated_at": "2025-12-24T19:00:00Z"
}

Delete Group

DELETE/groups/{group_id}

Delete a group. All group memberships will be removed.

Success Response (204 No Content)

No response body. The group and all its memberships have been deleted.

Example Request

await fetch('https://api.oauth42.com/groups/550e8400-e29b-41d4-a716-446655440000', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  }
});

Get Group Members

GET/groups/{group_id}/members

List all members of a group.

Success Response (200 OK)

{
  "members": [
    {
      "user_id": "user-uuid-1",
      "email": "[email protected]",
      "name": "John Doe",
      "joined_at": "2025-12-24T18:30:00Z"
    },
    {
      "user_id": "user-uuid-2",
      "email": "[email protected]",
      "name": "Jane Smith",
      "joined_at": "2025-12-24T18:35:00Z"
    }
  ],
  "total": 2
}

Add Group Member

POST/groups/{group_id}/members/{user_id}

Add a user to a group. The user will immediately see this group in their /oauth2/userinfo response.

Success Response (204 No Content)

No response body. The user has been added to the group.

Example Request

const groupId = '550e8400-e29b-41d4-a716-446655440000';
const userId = 'user-uuid-3';

await fetch(`https://api.oauth42.com/groups/${groupId}/members/${userId}`, {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
  }
});

// User now has 'acme_corp_admin' in their groups

Remove Group Member

DELETE/groups/{group_id}/members/{user_id}

Remove a user from a group. The group will be immediately removed from their /oauth2/userinfo response.

Success Response (204 No Content)

No response body. The user has been removed from the group.

Complete Multi-Tenant Example

Here's a complete example showing how to set up multi-tenant access control using groups:

// 1. Create tenant-specific groups
async function setupTenant(tenantName: string, accessToken: string) {
  // Create admin group
  const adminGroup = await fetch('https://api.oauth42.com/groups', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: `${tenantName}_admin`,
      description: `Administrators for ${tenantName}`
    })
  }).then(r => r.json());

  // Create read-only group
  const readGroup = await fetch('https://api.oauth42.com/groups', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: `${tenantName}_read`,
      description: `Read-only access to ${tenantName}`
    })
  }).then(r => r.json());

  return { adminGroup, readGroup };
}

// 2. Add users to tenant groups
async function grantTenantAccess(
  groupId: string,
  userId: string,
  accessToken: string
) {
  await fetch(`https://api.oauth42.com/groups/${groupId}/members/${userId}`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${accessToken}` }
  });
}

// 3. User logs in and their groups appear in userinfo
async function getUserTenants(userAccessToken: string) {
  const userinfo = await fetch('https://api.oauth42.com/oauth2/userinfo', {
    headers: { 'Authorization': `Bearer ${userAccessToken}` }
  }).then(r => r.json());

  // Parse groups to determine tenant access
  const tenants = userinfo.groups
    .filter((g: string) => g.endsWith('_admin') || g.endsWith('_read'))
    .map((g: string) => {
      const parts = g.split('_');
      const role = parts.pop(); // 'admin' or 'read'
      const tenant = parts.join('_');
      return { tenant, role };
    });

  return tenants;
  // Example: [
  //   { tenant: 'acme_corp', role: 'admin' },
  //   { tenant: 'globex', role: 'read' }
  // ]
}

// 4. Application checks tenant access
function checkTenantAccess(
  tenants: Array<{tenant: string, role: string}>,
  requiredTenant: string,
  requiredRole: 'admin' | 'read'
) {
  return tenants.some(t =>
    t.tenant === requiredTenant &&
    (t.role === 'admin' || t.role === requiredRole)
  );
}

// Usage
const { adminGroup, readGroup } = await setupTenant('acme_corp', adminToken);
await grantTenantAccess(adminGroup.id, userId, adminToken);

const tenants = await getUserTenants(userAccessToken);
const canAccess = checkTenantAccess(tenants, 'acme_corp', 'admin');

Error Responses

400

Bad Request

{
  "error": "invalid_request",
  "error_description": "Group name already exists"
}
401

Unauthorized

{
  "error": "unauthorized",
  "error_description": "Invalid or expired access token"
}
404

Not Found

{
  "error": "not_found",
  "error_description": "Group not found"
}

Related Endpoints