Organizations

Team/seat management — see Decisions → Organizations for why this is Phase 2, not the MVP itself.

  1. Endpoints
  2. GET /v1/organizations
  3. POST /v1/organizations
  4. GET /v1/organizations/{id}
  5. PATCH /v1/organizations/{id}
  6. GET /v1/organizations/{id}/members
  7. DELETE /v1/organizations/{id}/members/{userId}
  8. POST /v1/organizations/{id}/entitlements
  9. Delegated admin
  10. Errors specific to this resource

Endpoints

Method Path Purpose
GET /v1/organizations List (admin) or, for a non-admin caller, just the orgs they’re a member of.
POST /v1/organizations Create an org.
GET /v1/organizations/{id} Fetch one Organization.
PATCH /v1/organizations/{id} Update name, or set status to suspended/active — the only way to reach status: suspended.
GET /v1/organizations/{id}/members List member Users.
POST /v1/organizations/{id}/members Add a member (by user_id or by email invite), optionally as org_admin.
DELETE /v1/organizations/{id}/members/{userId} Remove a member.
GET /v1/organizations/{id}/entitlements List org-wide (“seat”) entitlements.
POST /v1/organizations/{id}/entitlements Grant an app to the whole org.

GET /v1/organizations

// Response — 200, non-admin caller (scoped to their own memberships)
{
  "data": [
    { "id": "org_01JAFZ8Y7X6W5V4U3T2S1R0Q9P", "name": "Acme Co.", "status": "active" }
  ],
  "page": { "next_cursor": null, "has_more": false }
}

Self-scoped: a non-admin caller sees every Organization they hold an OrganizationMembership in — not just one, since a User can belong to more than one Organization at once. A platform role with organizations.manage sees every Organization and may filter with ?status=active.

POST /v1/organizations

// Request
{ "name": "Acme Co." }
// Response — 201
{ "id": "org_01JAFZ8Y7X6W5V4U3T2S1R0Q9P", "name": "Acme Co.", "status": "active", "created_at": "2026-10-04T09:00:00Z" }

Requires a platform role with organizations.manage — self-service org creation (e.g. as part of a team-plan signup flow) is a product decision for Phase 3, not assumed here.

GET /v1/organizations/{id}

// Response — 200
{ "id": "org_01JAFZ8Y7X6W5V4U3T2S1R0Q9P", "name": "Acme Co.", "status": "active", "created_at": "2026-10-04T09:00:00Z" }

Any member of the org, or a platform role with organizations.manage.

PATCH /v1/organizations/{id}

// Request
{ "status": "suspended" }
// Response — 200, full updated object

Requires a platform role with organizations.manage — org admins can manage their own members and entitlements (see Delegated admin) but cannot suspend their own Organization. Suspending an Organization does not, by itself, touch any org-wide (source: org_seat) Entitlement — the same non-cascading default Decision #9 already established for a suspended Application; an admin revokes the affected Entitlements separately and explicitly if that’s actually warranted.

GET /v1/organizations/{id}/members

// Response — 200
{
  "data": [
    { "user_id": "usr_01JAG3Z9X8QS3F6K2M4N5P6R7S", "email": "jordan@example.com", "role": "org_admin", "joined_at": "2026-10-04T09:05:00Z" }
  ],
  "page": { "next_cursor": null, "has_more": false }
}

Any member of the org, or a platform role with organizations.manage. role is the member’s OrganizationMembership standing — org_admin or member — within this one Organization specifically.

DELETE /v1/organizations/{id}/members/{userId}

// Response — 204

Requires org-admin standing on this Organization (see Delegated admin) or a platform role with organizations.manage. Removes the member’s access to every org-wide (source: org_seat) Entitlement; any Entitlement granted to them individually is untouched.

POST /v1/organizations/{id}/entitlements

// Request
{
  "application_id": "app_invoicer",
  "source": "purchase",
  "order_id": "ord_01JAG8B3N4M5K6J7H8G9F0D1S2",
  "member_scope": "denylist",
  "member_overrides": ["usr_01JAG3Z9X8QS3F6K2M4N5P6R7S"]
}
// Response — 201
{
  "id": "ent_01JAGB2C3D4E5F6G7H8J9K0L1M",
  "organization_id": "org_01JAFZ8Y7X6W5V4U3T2S1R0Q9P",
  "user_id": null,
  "application_id": "app_invoicer",
  "status": "active",
  "source": "org_seat",
  "member_scope": "denylist",
  "member_overrides": ["usr_01JAG3Z9X8QS3F6K2M4N5P6R7S"]
}

Requires a platform role with organizations.manage, or org-admin standing on this Organization. member_scope/member_overrides are optional and default to all_members/none — every current and future member of the org inherits access without a per-member grant — see Domain Model → Entitlements → Org-wide entitlements for what allowlist/denylist change. effective_permissions for an included member still resolves their own app-scoped Role on top of this; the org entitlement only answers the on/off question, same as a personal one would. An org admin can PATCH this same resource (see Entitlements) to change member_scope/member_overrides later without re-granting the whole app.

Delegated admin

An org admin is simply a User whose OrganizationMembership role is org_admin for that specific Organization — not a third Role scope alongside platform and application_id. This standing lets them manage their own org’s members (POST/DELETE on /members) and org-wide Entitlements (POST/PATCH on /entitlements, including member_scope) without needing a platform-wide admin Role — and says nothing about their standing in any other Organization, the same independence AppRole already has across Applications. See Decisions → Tenant vs. Organization for why this is unrelated to infrastructure-level admin (tenants.manage), which an org admin never holds.

Errors specific to this resource

Code When
already_member Adding a user who’s already a member.
member_has_active_app_sessions Removing a member while they hold an app-scoped Role that would otherwise orphan it — the Role assignment is removed along with the membership, not blocked; this code labels the response so a client can surface what else changed.
member_override_not_a_member A user_id in member_overrides isn’t actually a member of this Organization.

Back to top

Substratal Apps Platform API — living specification. This site is the system of record; see git history for how it has changed over time.

This site uses Just the Docs, a documentation theme for Jekyll.