Groups
GET /orgs/{orgId}/api/v1/admin/groups
POST /orgs/{orgId}/api/v1/admin/groups
GET /orgs/{orgId}/api/v1/admin/groups/{groupId}
PUT /orgs/{orgId}/api/v1/admin/groups/{groupId}
DELETE /orgs/{orgId}/api/v1/admin/groups/{groupId}
GET /orgs/{orgId}/api/v1/admin/groups/{groupId}/members
POST /orgs/{orgId}/api/v1/admin/groups/{groupId}/members
DELETE /orgs/{orgId}/api/v1/admin/groups/{groupId}/members/{userId}
GET /orgs/{orgId}/api/v1/admin/groups/{groupId}/roles
POST /orgs/{orgId}/api/v1/admin/groups/{groupId}/roles
DELETE /orgs/{orgId}/api/v1/admin/groups/{groupId}/roles/{roleId}
Groups organize users and carry role assignments. Users inherit all roles from their groups at token issuance time.
Authentication
All group management endpoints require an admin API key or a Bearer token with admin:groups:read / admin:groups:write.
Each endpoint needs the credential's scope and a permission. A session or Bearer caller must
hold the permission; for an API key its creator must still hold it. A missing permission
returns 403 forbidden with details.required_permissions. See
Required permissions.
| Method | Endpoint | Permission | API-key / token scope |
|---|---|---|---|
| GET | /admin/groups | groups.view | admin:groups:read |
| GET | /admin/groups/{groupId} | groups.view | admin:groups:read |
| POST | /admin/groups | groups.create | admin:groups:write |
| PUT PATCH | /admin/groups/{groupId} | groups.edit | admin:groups:write |
| DELETE | /admin/groups/{groupId} | groups.delete | admin:groups:write |
| GET | /admin/groups/{groupId}/members | groups.view | admin:groups:read |
| POST | /admin/groups/{groupId}/members | groups.edit | admin:groups:write |
| DELETE | /admin/groups/{groupId}/members/{userId} | groups.edit | admin:groups:write |
| GET | /admin/groups/{groupId}/roles | groups.view | admin:groups:read |
| POST | /admin/groups/{groupId}/roles | groups.edit | admin:groups:write |
| DELETE | /admin/groups/{groupId}/roles/{roleId} | groups.edit | admin:groups:write |
| PUT | /admin/groups/{groupId}/roles | groups.edit | admin:groups:write |
The Group Object
Single-group responses are wrapped in a data envelope. roles and members are arrays of objects (not slug strings) and are returned only on the single-group GET /groups/{groupId} (detailed) response — list rows omit them.
{
"data": {
"id": "01JF3KABCDE...",
"name": "Engineering Team",
"slug": "engineering",
"description": "Software engineers and SREs",
"isSystem": false,
"memberCount": 24,
"roleCount": 2,
"createdAt": "2026-01-15T10:30:00Z",
"roles": [
{ "id": 12, "name": "Developer", "slug": "developer" },
{ "id": 13, "name": "Deploy", "slug": "deploy" }
],
"members": [
{ "id": "01JF3KUSER...", "email": "alice@acme.com", "name": "Alice" }
],
"updatedAt": "2026-03-01T08:00:00Z"
}
}
There is no displayName field — a group is identified by name and a URL-safe slug. Sending displayName in a create/update body is ignored.
List Groups
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups?page=1&limit=50" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups?page=1&limit=50", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups?page=1&limit=50",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups?page=1&limit=50", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Query Parameters
| Parameter | Description |
|---|---|
page | Page number (default: 1) |
limit | Results per page (default: 25, max: 100) |
search | Filter by name, slug, or description |
Create Group
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"name": "Engineering Team",
"slug": "engineering",
"description": "Software engineers and SREs",
"roles": ["developer", "deploy"]
}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
"name": "Engineering Team",
"slug": "engineering",
"description": "Software engineers and SREs",
"roles": ["developer", "deploy"]
})
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={
"name": "Engineering Team",
"slug": "engineering",
"description": "Software engineers and SREs",
"roles": ["developer", "deploy"]
}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{
"name": "Engineering Team",
"slug": "engineering",
"description": "Software engineers and SREs",
"roles": ["developer", "deploy"]
}`)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Returns 201 Created with the new group object. name is required; if slug is omitted it is derived from name. roles is an optional array of role slugs to assign at creation time (only roles belonging to this organization are accepted).
Update Group
- curl
- Node
- Python
- Go
curl -X PUT https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE... \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"name": "Engineering & SRE",
"description": "Software engineers, SREs, and DevOps"
}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE...", {
method: "PUT",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
"name": "Engineering & SRE",
"description": "Software engineers, SREs, and DevOps"
})
});
console.log(await resp.json());
import os, requests
resp = requests.put(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE...",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={
"name": "Engineering & SRE",
"description": "Software engineers, SREs, and DevOps"
}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{
"name": "Engineering & SRE",
"description": "Software engineers, SREs, and DevOps"
}`)
req, _ := http.NewRequest("PUT", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE...", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Members
List Members
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Add Members
Accepts a single userId, an array userIds, or a users array.
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{"userId": "01JF3KUSER..."}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({"userId": "01JF3KUSER..."})
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={"userId": "01JF3KUSER..."}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{"userId": "01JF3KUSER..."}`)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{"userIds": ["01JF3KUSER...", "01JF3KUSER2..."]}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({"userIds": ["01JF3KUSER...", "01JF3KUSER2..."]})
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={"userIds": ["01JF3KUSER...", "01JF3KUSER2..."]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{"userIds": ["01JF3KUSER...", "01JF3KUSER2..."]}`)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Remove Member
- curl
- Node
- Python
- Go
curl -X DELETE \
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members/01JF3KUSER..." \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members/01JF3KUSER...", {
method: "DELETE",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.delete(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members/01JF3KUSER...",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("DELETE", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../members/01JF3KUSER...", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Role Assignment
Roles assigned to a group are inherited by all group members.
System roles cannot be assigned to groups.
Get Group Roles
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Add a Role to a Group
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{"roleId": "01JF3KROLE..."}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({"roleId": "01JF3KROLE..."})
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={"roleId": "01JF3KROLE..."}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{"roleId": "01JF3KROLE..."}`)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Remove a Role from a Group
- curl
- Node
- Python
- Go
curl -X DELETE "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles/01JF3KROLE..." \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles/01JF3KROLE...", {
method: "DELETE",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.delete(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles/01JF3KROLE...",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("DELETE", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE.../roles/01JF3KROLE...", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Delete Group
- curl
- Node
- Python
- Go
curl -X DELETE "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE..." \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE...", {
method: "DELETE",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.delete(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE...",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("DELETE", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/groups/01JF3KABCDE...", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Returns 200 on success. Members are not deleted — only their group membership is removed.