API Simplifications - Logto Organization Management
Overview
The API has been simplified to reduce complexity around Logto organization binding and synchronization.
What Changed
❌ REMOVED Endpoints
| Endpoint | Method | Reason |
|---|---|---|
/admin/logto/orgs | GET | No longer needed - orgs are auto-created with firms |
/admin/logto/orgs/{lawFirmId}/sync | POST | No manual sync - orgs stay in sync automatically |
❌ REMOVED Schema Fields
| Schema | Field | Reason |
|---|---|---|
LawFirm | logtoSyncedAt | No sync tracking needed |
LogtoOrg | (entire schema) | Not exposed via API |
CreateLawFirmRequest | createLogtoOrg | Always creates org automatically |
CreateLawFirmRequest | logtoOrgId | Can't bind to existing orgs |
✅ SIMPLIFIED Behavior
Law Firm Creation
Before (complex):
POST /admin/law-firms
{
"name": "Acme Legal",
"slug": "acme-legal",
"createLogtoOrg": true, // ❌ Removed - always true
"logtoOrgId": "org_existing" // ❌ Removed - can't bind to existing
}
After (simple):
POST /admin/law-firms
{
"name": "Acme Legal",
"slug": "acme-legal"
// Logto org is ALWAYS auto-created
}
Response (unchanged):
{
"id": "firm_abc123",
"name": "Acme Legal",
"slug": "acme-legal",
"logtoOrgId": "org_xyz789", // ✅ Auto-created
"createdAt": "2025-10-18T12:00:00Z",
"updatedAt": "2025-10-18T12:00:00Z"
}
Law Firm Deletion
When deleting a law firm:
DELETE /admin/law-firms/{lawFirmId}
Automatic cleanup:
- Law firm record deleted
- Logto organization automatically deleted
- All org memberships removed
- No orphaned organizations
✅ KEPT Endpoints
All organization member management endpoints remain:
| Endpoint | Method | Purpose |
|---|---|---|
/admin/logto/orgs/{lawFirmId}/members | GET | List org members |
/admin/logto/orgs/{lawFirmId}/members | POST | Add/invite member |
/admin/logto/orgs/{lawFirmId}/members/{userId} | GET | Get member details |
/admin/logto/orgs/{lawFirmId}/members/{userId} | DELETE | Remove member |
/admin/logto/orgs/{lawFirmId}/members/{userId}/roles | PUT | Update member roles |
/admin/logto/org-roles | GET | List available roles |
Rationale
Problems with Old Design
- Complexity: Multiple ways to create orgs (auto, manual, bind existing)
- Sync issues: Manual sync endpoint could get out of sync
- Edge cases: What if sync fails? What if org deleted in Logto but not in app?
- Confusion: When to use
createLogtoOrgvslogtoOrgId?
Benefits of New Design
- Predictable: One law firm = one Logto org, always
- No drift: Org lifecycle tied to firm lifecycle
- Atomic: Create/delete operations are all-or-nothing
- Simple: No manual sync or binding to worry about
- Maintainable: Less code, fewer edge cases
Migration Guide
If You Were Using createLogtoOrg: true
Before:
const response = await fetch('/admin/law-firms', {
method: 'POST',
body: JSON.stringify({
name: 'Acme Legal',
slug: 'acme-legal',
createLogtoOrg: true // ❌ Remove this
})
});
After:
const response = await fetch('/admin/law-firms', {
method: 'POST',
body: JSON.stringify({
name: 'Acme Legal',
slug: 'acme-legal'
// Logto org created automatically
})
});
If You Were Using Manual Org Binding
Before:
// ❌ This no longer works
const response = await fetch('/admin/law-firms', {
method: 'POST',
body: JSON.stringify({
name: 'Acme Legal',
slug: 'acme-legal',
logtoOrgId: 'org_existing123'
})
});
After:
// ✅ Org is always auto-created, can't bind to existing
const response = await fetch('/admin/law-firms', {
method: 'POST',
body: JSON.stringify({
name: 'Acme Legal',
slug: 'acme-legal'
})
});
// Response will include auto-created logtoOrgId
If You Were Using Sync Endpoint
Before:
// ❌ This endpoint no longer exists
await fetch(`/admin/logto/orgs/${lawFirmId}/sync`, {
method: 'POST'
});
After:
// ✅ No sync needed - always in sync
// Member management endpoints work directly:
await fetch(`/admin/logto/orgs/${lawFirmId}/members`, {
method: 'POST',
body: JSON.stringify({
logtoUserId: 'user_123',
orgRoles: ['admin']
})
});
Updated OpenAPI Changes Needed
components.schemas.LawFirm
Remove:
logtoSyncedAt: { type: string, format: date-time, nullable: true }
Keep:
LawFirm:
type: object
properties:
id: { type: string }
name: { type: string }
slug: { type: string }
logtoOrgId: { type: string } # Always set, never null
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
components.schemas.CreateLawFirmRequest
Remove:
createLogtoOrg: { type: boolean }
logtoOrgId: { type: string }
Keep:
CreateLawFirmRequest:
type: object
required: [name, slug]
properties:
name: { type: string }
slug: { type: string }
# Optional contact fields
address: { type: string }
phone: { type: string }
email: { type: string }
paths - Remove These
# ❌ Remove GET /admin/logto/orgs
/admin/logto/orgs:
get: ... # DELETE THIS ENTIRE ENDPOINT
# ❌ Remove POST /admin/logto/orgs/{lawFirmId}/sync
/admin/logto/orgs/{lawFirmId}/sync:
post: ... # DELETE THIS ENTIRE ENDPOINT
paths - Keep These
# ✅ Keep all member management endpoints
/admin/logto/orgs/{lawFirmId}/members:
get: ... # ✅ KEEP
post: ... # ✅ KEEP
/admin/logto/orgs/{lawFirmId}/members/{userId}:
get: ... # ✅ KEEP
delete: ... # ✅ KEEP
/admin/logto/orgs/{lawFirmId}/members/{userId}/roles:
put: ... # ✅ KEEP
/admin/logto/org-roles:
get: ... # ✅ KEEP
Summary
The simplified design:
- ✅ Always creates Logto org with law firm
- ✅ Always deletes Logto org with law firm
- ✅ No manual binding to existing orgs
- ✅ No manual sync endpoints
- ✅ Member management still fully supported
- ✅ Simpler, more predictable, easier to maintain