> ## Documentation Index
> Fetch the complete documentation index at: https://docs.safeclose.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Query workspace memberships and user profile

> Retrieve your organization memberships, active organization context, full org bundle, user profile, and the combined menu-core payload for navigation.

The workspace membership endpoints give you read access to everything your authenticated user belongs to: which organizations you are a member of, which one is currently active, a full bundle of a single org's data, and your own user profile. These are the endpoints that power the Safeclose navigation shell — you can call them directly to bootstrap your integration or to validate membership state before executing org-scoped actions.

All endpoints require a valid Bearer token. See [Authenticate requests](/api/authentication) for details.

***

## List memberships

```http theme={null}
GET /v1/workspace/memberships
```

Returns every organization the authenticated user is an **active** member of. Each item in the array combines the membership record, the organization it belongs to (including billing account and counts), and the role assigned to you.

### Request headers

<ParamField header="Authorization" type="string" required>
  Bearer token from your Clerk session. Example: `Bearer eyJhbGc...`
</ParamField>

### Response

Returns a JSON array. Each element has the following shape:

<ResponseField name="[].id" type="string" required>
  Membership record ID.
</ResponseField>

<ResponseField name="[].userId" type="string" required>
  The authenticated user's ID.
</ResponseField>

<ResponseField name="[].status" type="string" required>
  Always `"ACTIVE"` for records returned by this endpoint.
</ResponseField>

<ResponseField name="[].organizationId" type="string" required>
  ID of the organization this membership belongs to.
</ResponseField>

<ResponseField name="[].createdAt" type="string" required>
  ISO 8601 timestamp when the membership was created.
</ResponseField>

<ResponseField name="[].organization" type="object" required>
  The organization record.

  <Expandable title="organization properties">
    <ResponseField name="id" type="string" required>
      Organization ID.
    </ResponseField>

    <ResponseField name="name" type="string" required>
      Display name of the organization.
    </ResponseField>

    <ResponseField name="slug" type="string" required>
      URL-safe slug. Unique across all organizations. Lowercase alphanumeric with hyphens, 2–48 characters.
    </ResponseField>

    <ResponseField name="ownerUserId" type="string" required>
      User ID of the organization owner. Owners always have all permissions.
    </ResponseField>

    <ResponseField name="brandName" type="string or null" required>
      Custom brand display name, or `null` if not configured.
    </ResponseField>

    <ResponseField name="brandLogoUrl" type="string or null" required>
      URL to the organization's logo image, or `null` if not configured.
    </ResponseField>

    <ResponseField name="brandPrimaryHex" type="string or null" required>
      Primary brand color as a `#RRGGBB` hex string, or `null` if not configured.
    </ResponseField>

    <ResponseField name="createdAt" type="string" required>
      ISO 8601 timestamp when the organization was created.
    </ResponseField>

    <ResponseField name="billingAccount" type="object or null" required>
      The organization's billing account, or `null` if not yet provisioned.

      <Expandable title="billingAccount properties">
        <ResponseField name="id" type="string" required>
          Billing account ID.
        </ResponseField>

        <ResponseField name="organizationId" type="string" required>
          Parent organization ID.
        </ResponseField>

        <ResponseField name="planTier" type="string" required>
          Current subscription tier. One of `"FREE"`, `"STARTER"`, `"PRO"`, `"ENTERPRISE"`.
        </ResponseField>

        <ResponseField name="status" type="string" required>
          Billing status. One of `"NONE"`, `"ACTIVE"`, `"PAST_DUE"`, `"CANCELED"`.
        </ResponseField>

        <ResponseField name="stripeCustomerId" type="string or null" required>
          Stripe customer ID, or `null` if not connected.
        </ResponseField>

        <ResponseField name="stripeSubscriptionId" type="string or null" required>
          Stripe subscription ID, or `null` if not on a paid plan.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="_count" type="object" required>
      Aggregate counts for the organization.

      <Expandable title="_count properties">
        <ResponseField name="members" type="number" required>
          Total number of active members.
        </ResponseField>

        <ResponseField name="locations" type="number" required>
          Total number of configured locations.
        </ResponseField>

        <ResponseField name="roles" type="number" required>
          Total number of roles (built-in and custom).
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="[].role" type="object" required>
  The role assigned to you in this organization.

  <Expandable title="role properties">
    <ResponseField name="id" type="string" required>
      Role ID.
    </ResponseField>

    <ResponseField name="name" type="string" required>
      Human-readable role name, e.g. `"Owner"` or `"Agent"`.
    </ResponseField>

    <ResponseField name="slug" type="string" required>
      URL-safe slug for the role, e.g. `"owner"` or `"agent"`.
    </ResponseField>

    <ResponseField name="isBuiltIn" type="boolean" required>
      `true` for the system-created `owner` role; `false` for custom roles.
    </ResponseField>

    <ResponseField name="permissions" type="object" required>
      A JSON object where each key is a permission key and the value is `true` or `false`. See the [org permission model](/api/workspace/organizations#org-permission-model) for the full list of keys.
    </ResponseField>
  </Expandable>
</ResponseField>

### Example

<CodeGroup>
  ```bash Request theme={null}
  curl --request GET \
    --url https://your-api.example.com/v1/workspace/memberships \
    --header 'Authorization: Bearer <your-session-token>'
  ```

  ```json Response theme={null}
  [
    {
      "id": "mem_01j9z8x7w6v5u4t3s2r1q0p",
      "userId": "user_clerk_abc123",
      "status": "ACTIVE",
      "organizationId": "org_01j9z8aabbccdd",
      "createdAt": "2025-09-01T08:00:00.000Z",
      "organization": {
        "id": "org_01j9z8aabbccdd",
        "name": "Acme Auto Finance",
        "slug": "acme-auto-finance",
        "ownerUserId": "user_clerk_abc123",
        "brandName": "Acme",
        "brandLogoUrl": "https://cdn.example.com/acme-logo.png",
        "brandPrimaryHex": "#1A56DB",
        "createdAt": "2025-09-01T07:50:00.000Z",
        "billingAccount": {
          "id": "bill_01j9z8bbccddee",
          "organizationId": "org_01j9z8aabbccdd",
          "planTier": "PRO",
          "status": "ACTIVE",
          "stripeCustomerId": "cus_AbCdEfGhIjKl",
          "stripeSubscriptionId": "sub_AbCdEfGhIjKl"
        },
        "_count": {
          "members": 8,
          "locations": 3,
          "roles": 4
        }
      },
      "role": {
        "id": "role_01j9z8ccddee",
        "name": "Owner",
        "slug": "owner",
        "isBuiltIn": true,
        "permissions": {
          "orgSettings": true,
          "brand": true,
          "locations": true,
          "roles": true,
          "members": true,
          "billing": true
        }
      }
    }
  ]
  ```
</CodeGroup>

***

## Get active organization ID

```http theme={null}
GET /v1/workspace/active-org-id
```

Returns the ID of the organization currently marked active for your session. Safeclose resolves this by looking up the `activeOrganizationId` stored in your user profile and confirming that you are still an active member. If the stored value is stale (e.g., you were removed), the API clears it and falls back to the first organization in your membership list, alphabetically. Returns `null` if you have no active memberships.

### Request headers

<ParamField header="Authorization" type="string" required>
  Bearer token from your Clerk session.
</ParamField>

### Response

<ResponseField name="activeOrganizationId" type="string or null" required>
  ID of the currently active organization, or `null` if none is set or you have no memberships.
</ResponseField>

### Example

<CodeGroup>
  ```bash Request theme={null}
  curl --request GET \
    --url https://your-api.example.com/v1/workspace/active-org-id \
    --header 'Authorization: Bearer <your-session-token>'
  ```

  ```json Response theme={null}
  {
    "activeOrganizationId": "org_01j9z8aabbccdd"
  }
  ```
</CodeGroup>

***

## Get active organization summary

```http theme={null}
GET /v1/workspace/active-org-summary
```

Returns the ID and display name of the currently active organization. Convenient when you only need to show the org name in a header or breadcrumb without loading full membership data.

### Request headers

<ParamField header="Authorization" type="string" required>
  Bearer token from your Clerk session.
</ParamField>

### Response

<ResponseField name="activeOrganizationId" type="string or null" required>
  ID of the currently active organization, or `null` if none is resolved.
</ResponseField>

<ResponseField name="organizationName" type="string or null" required>
  Display name of the active organization, or `null` if none is resolved.
</ResponseField>

### Example

<CodeGroup>
  ```bash Request theme={null}
  curl --request GET \
    --url https://your-api.example.com/v1/workspace/active-org-summary \
    --header 'Authorization: Bearer <your-session-token>'
  ```

  ```json Response theme={null}
  {
    "activeOrganizationId": "org_01j9z8aabbccdd",
    "organizationName": "Acme Auto Finance"
  }
  ```
</CodeGroup>

***

## Get organization bundle

```http theme={null}
GET /v1/workspace/org-bundle/:orgId
```

Returns the complete data bundle for a single organization: the full org record (including billing and branding), all locations ordered by `sortOrder`, all roles ordered by slug, and all members ordered by `createdAt`. Also includes your own membership and role within the org.

Use this endpoint to populate organization settings screens or to inspect the full permission set for a given org before performing actions.

<Note>
  This endpoint returns `404` if you are not an active member of the requested organization. You cannot use it to inspect orgs you have not joined.
</Note>

### Path parameters

<ParamField path="orgId" type="string" required>
  ID of the organization to retrieve.
</ParamField>

### Request headers

<ParamField header="Authorization" type="string" required>
  Bearer token from your Clerk session.
</ParamField>

### Response

The response is an `OrgMember` record for your own membership, with the full organization and its nested collections included.

<ResponseField name="id" type="string" required>
  Your membership record ID.
</ResponseField>

<ResponseField name="userId" type="string" required>
  Your user ID.
</ResponseField>

<ResponseField name="status" type="string" required>
  Always `"ACTIVE"`.
</ResponseField>

<ResponseField name="role" type="object" required>
  Your assigned role in this organization. Same shape as the `role` object in [list memberships](#list-memberships).
</ResponseField>

<ResponseField name="organization" type="object" required>
  The full organization record.

  <Expandable title="organization properties">
    <ResponseField name="id" type="string" required>
      Organization ID.
    </ResponseField>

    <ResponseField name="name" type="string" required>
      Display name.
    </ResponseField>

    <ResponseField name="slug" type="string" required>
      URL-safe slug.
    </ResponseField>

    <ResponseField name="ownerUserId" type="string" required>
      User ID of the organization owner.
    </ResponseField>

    <ResponseField name="brandName" type="string or null" required>
      Custom brand name, or `null`.
    </ResponseField>

    <ResponseField name="brandLogoUrl" type="string or null" required>
      Logo URL, or `null`.
    </ResponseField>

    <ResponseField name="brandPrimaryHex" type="string or null" required>
      Primary hex color (`#RRGGBB`), or `null`.
    </ResponseField>

    <ResponseField name="billingAccount" type="object or null" required>
      Billing account. Same shape as in [list memberships](#list-memberships).
    </ResponseField>

    <ResponseField name="locations" type="object[]" required>
      All org locations, ordered by `sortOrder` ascending.

      <Expandable title="location properties">
        <ResponseField name="id" type="string" required>
          Location ID.
        </ResponseField>

        <ResponseField name="organizationId" type="string" required>
          Parent organization ID.
        </ResponseField>

        <ResponseField name="name" type="string" required>
          Location display name. Maximum 120 characters.
        </ResponseField>

        <ResponseField name="addressLine1" type="string or null" required>
          Street address, or `null`.
        </ResponseField>

        <ResponseField name="city" type="string or null" required>
          City, or `null`.
        </ResponseField>

        <ResponseField name="region" type="string or null" required>
          State, province, or region, or `null`.
        </ResponseField>

        <ResponseField name="postalCode" type="string or null" required>
          Postal code, or `null`.
        </ResponseField>

        <ResponseField name="countryCode" type="string or null" required>
          ISO 3166-1 alpha-2 country code in uppercase, e.g. `"US"`, or `null`.
        </ResponseField>

        <ResponseField name="phone" type="string or null" required>
          Phone number, or `null`.
        </ResponseField>

        <ResponseField name="sortOrder" type="number" required>
          Relative display order. Locations are returned ascending by this value.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="roles" type="object[]" required>
      All roles defined for the organization, ordered by slug ascending.

      <Expandable title="role properties">
        <ResponseField name="id" type="string" required>
          Role ID.
        </ResponseField>

        <ResponseField name="name" type="string" required>
          Role display name. Maximum 80 characters.
        </ResponseField>

        <ResponseField name="slug" type="string" required>
          URL-safe slug. Maximum 32 characters.
        </ResponseField>

        <ResponseField name="isBuiltIn" type="boolean" required>
          `true` for the system `owner` role.
        </ResponseField>

        <ResponseField name="permissions" type="object" required>
          Permission map. See the [org permission model](/api/workspace/organizations#org-permission-model).
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="members" type="object[]" required>
      All members of the organization, ordered by `createdAt` ascending.

      <Expandable title="member properties">
        <ResponseField name="id" type="string" required>
          Membership record ID.
        </ResponseField>

        <ResponseField name="userId" type="string" required>
          User ID. For pending email invites, this is `"invite:<email>"`.
        </ResponseField>

        <ResponseField name="status" type="string" required>
          Membership status. `"ACTIVE"` for joined members; `"INVITED"` for pending invites.
        </ResponseField>

        <ResponseField name="invitedEmail" type="string or null" required>
          Email address for a pending invite, or `null` for active members.
        </ResponseField>

        <ResponseField name="createdAt" type="string" required>
          ISO 8601 timestamp when the membership was created.
        </ResponseField>

        <ResponseField name="role" type="object" required>
          The role assigned to this member.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

### Error responses

| Status | Body                        | Cause                                                       |
| ------ | --------------------------- | ----------------------------------------------------------- |
| `404`  | `{ "error": "Not found." }` | You are not an active member of the requested organization. |

***

## Get user profile

```http theme={null}
GET /v1/workspace/profile
```

Returns your user profile record, or `null` if no profile has been created yet. A profile is automatically provisioned the first time you create or join an organization, or when you call the [`upsertUserProfile` action](/api/workspace/organizations#upsert-user-profile).

### Request headers

<ParamField header="Authorization" type="string" required>
  Bearer token from your Clerk session.
</ParamField>

### Response

<ResponseField name="profile" type="object or null" required>
  Your user profile, or `null` if no profile record exists.

  <Expandable title="profile properties">
    <ResponseField name="id" type="string" required>
      Profile record ID.
    </ResponseField>

    <ResponseField name="userId" type="string" required>
      Your user ID (matches the authenticated token).
    </ResponseField>

    <ResponseField name="displayName" type="string or null" required>
      Preferred display name. Maximum 120 characters, or `null`.
    </ResponseField>

    <ResponseField name="title" type="string or null" required>
      Job title or role description. Maximum 120 characters, or `null`.
    </ResponseField>

    <ResponseField name="bio" type="string or null" required>
      Short biography. Maximum 2000 characters, or `null`.
    </ResponseField>

    <ResponseField name="avatarUrl" type="string or null" required>
      URL to your avatar image. Maximum 2000 characters, or `null`.
    </ResponseField>

    <ResponseField name="tileAccent" type="string or null" required>
      Accent color or style token for profile tile display. Maximum 32 characters, or `null`.
    </ResponseField>

    <ResponseField name="publicSlug" type="string or null" required>
      Public profile slug used in shareable URLs. Lowercase alphanumeric with hyphens, 2–48 characters, or `null`.
    </ResponseField>

    <ResponseField name="activeOrganizationId" type="string or null" required>
      ID of the organization currently set as active for this user, or `null`.
    </ResponseField>

    <ResponseField name="createdAt" type="string" required>
      ISO 8601 timestamp when the profile was created.
    </ResponseField>

    <ResponseField name="updatedAt" type="string" required>
      ISO 8601 timestamp of the most recent update.
    </ResponseField>
  </Expandable>
</ResponseField>

### Example

<CodeGroup>
  ```bash Request theme={null}
  curl --request GET \
    --url https://your-api.example.com/v1/workspace/profile \
    --header 'Authorization: Bearer <your-session-token>'
  ```

  ```json Response theme={null}
  {
    "profile": {
      "id": "prof_01j9z8ddeeff",
      "userId": "user_clerk_abc123",
      "displayName": "Jordan Smith",
      "title": "Compliance Officer",
      "bio": "Specializing in chattel document compliance for manufactured housing.",
      "avatarUrl": "https://cdn.example.com/avatars/jordan.jpg",
      "tileAccent": "blue",
      "publicSlug": "jordan-smith",
      "activeOrganizationId": "org_01j9z8aabbccdd",
      "createdAt": "2025-09-01T08:00:00.000Z",
      "updatedAt": "2026-03-15T10:30:00.000Z"
    }
  }
  ```
</CodeGroup>

***

## Get menu core

```http theme={null}
GET /v1/workspace/menu-core
```

Returns your user profile, the full memberships array, and the active organization ID in a single request. This is the same data that powers the Safeclose navigation shell, fetched in parallel from the three underlying sources. Use this endpoint to bootstrap a client application or to hydrate server-side rendering in a single round trip.

### Request headers

<ParamField header="Authorization" type="string" required>
  Bearer token from your Clerk session.
</ParamField>

### Response

<ResponseField name="profile" type="object or null" required>
  Your user profile. Same shape as the `profile` field in [get user profile](#get-user-profile).
</ResponseField>

<ResponseField name="memberships" type="object[]" required>
  Your organization memberships. Same shape as the array returned by [list memberships](#list-memberships).
</ResponseField>

<ResponseField name="activeOrganizationId" type="string or null" required>
  The currently active organization ID. Same value as [get active organization ID](#get-active-organization-id).
</ResponseField>

<Tip>
  Prefer this endpoint over calling the three individual endpoints separately when your use case requires all three values at startup.
</Tip>

***

## Error cases

| Status | Error message                         | Cause                                                    |
| ------ | ------------------------------------- | -------------------------------------------------------- |
| `401`  | `Missing Authorization Bearer token.` | No `Authorization` header was sent.                      |
| `401`  | `Invalid or expired token.`           | The Bearer token is malformed or has expired.            |
| `404`  | `{ "error": "Not found." }`           | Org bundle requested for an org you are not a member of. |
