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 tenantacme_corp_read- Read-only access to Acme Corpglobex_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_TOKENGroups 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:
| Operation | Method | Required Scope |
|---|---|---|
| List groups | GET /groups | read or admin |
| Get group details | GET /groups/{id} | read or admin |
| Get group members | GET /groups/{id}/members | read or admin |
| Create group | POST /groups | write or admin |
| Update group | PUT /groups/{id} | write or admin |
| Add group member | POST /groups/{id}/members/{user_id} | write or admin |
| Delete group | DELETE /groups/{id} | admin only |
| Remove group member | DELETE /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 ForbiddenCreate Group
/groupsCreate 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"]
}namerequiredUnique group name. Must be unique within your customer account.
descriptionoptionalHuman-readable description of the group's purpose.
organization_idoptionalUUID of organization to scope this group to.
member_idsoptionalArray 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
/groupsList 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
/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
/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
/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
/groups/{group_id}/membersList 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
/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 groupsRemove Group Member
/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
Bad Request
{
"error": "invalid_request",
"error_description": "Group name already exists"
}Unauthorized
{
"error": "unauthorized",
"error_description": "Invalid or expired access token"
}Not Found
{
"error": "not_found",
"error_description": "Group not found"
}