# Groups

## List

**get** `/zones/{zoneId}/groups`

Returns a paginated list of the groups in the specified zone. Use cursor pagination via `after`/`before`. Sort: comma-separated field list; prefix with `-` for descending (allowed: created_at, name, identifier). Pass `expand[]=member_count` to include each group's member count, `expand[]=roles` to include the identifiers of the roles assigned to each group, and `expand[]=total_count` to include the matching row count. Filter by exact identifier via `filter[identifier]` (repeatable, OR'd across values). Search via `query[]` (case-insensitive substring match, OR'd across repeated values); it matches the group's name and identifier. Pass `filter[id]` (repeatable, max 100) to restrict results to a known set of groups — mutually exclusive with `after`/`before` (returns 400 if combined). When `filter[id]` is set, `limit` is ignored and the response contains every requested group that exists in the zone, in a single page. IDs not in the zone are silently omitted.

### Path Parameters

- `zoneId: string`

### Query Parameters

- `after: optional string`

  Cursor for forward pagination

- `before: optional string`

  Cursor for backward pagination

- `"expand[]": optional "total_count" or "member_count" or "roles" or array of "total_count" or "member_count" or "roles"`

  - `UnionMember0 = "total_count" or "member_count" or "roles"`

    - `"total_count"`

    - `"member_count"`

    - `"roles"`

  - `UnionMember1 = array of "total_count" or "member_count" or "roles"`

    - `"total_count"`

    - `"member_count"`

    - `"roles"`

- `"filter[id]": optional string or array of string`

  Restrict results to groups with this ID. Repeatable, max 100. Mutually exclusive with after/before.

  - `UnionMember0 = string`

    Restrict results to groups with this ID. Repeatable, max 100. Mutually exclusive with after/before.

  - `UnionMember1 = array of string`

- `"filter[identifier]": optional string or array of string`

  Filter by exact group identifier

  - `UnionMember0 = string`

    Filter by exact group identifier

  - `UnionMember1 = array of string`

- `limit: optional number`

  Maximum number of items to return

- `"query[]": optional string or array of string`

  Search across name and identifier (substring match)

  - `UnionMember0 = string`

    Search across name and identifier (substring match)

  - `UnionMember1 = array of string`

- `sort: optional string`

  Comma-separated sort fields. Prefix with - for descending. Allowed: created_at, name, identifier

### Returns

- `items: array of Group`

  - `id: string`

    Unique identifier of the group

  - `created_at: string`

    Entity creation timestamp

  - `external: boolean`

    Whether the group is synced from an external directory. When true the group is directory-owned and its membership is read-only; when false it is managed in Keycard. Read-only: set by external sync, never by the caller.

  - `identifier: string`

    User-specified identifier, unique within the zone. Automatically assigned for groups from an external directory.

  - `name: string`

    Human-readable group name

  - `organization_id: string`

    Organization this group belongs to

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this group belongs to

  - `member_count: optional number`

    Number of users in the group. Included only when requested via `expand[]=member_count` (group get or list).

  - `roles: optional array of string`

    Identifiers of the roles assigned to the group; members inherit them. Deduped across scopes. Included only when requested via `expand[]=roles` (group get or list).

- `pagination: object { after_cursor, before_cursor, total_count }`

  Cursor-based pagination metadata

  - `after_cursor: string`

    An opaque cursor used for paginating through a list of results

  - `before_cursor: string`

    An opaque cursor used for paginating through a list of results

  - `total_count: optional number`

    Total number of items matching the query. Only included when expand[]=total_count is requested.

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Create

**post** `/zones/{zoneId}/groups`

Creates a group in the zone (managed in Keycard). Groups synced from an external directory are created by that directory, not here.

### Path Parameters

- `zoneId: string`

### Body Parameters

- `name: string`

  Human-readable group name

- `identifier: optional string`

  User-specified identifier, unique within the zone. Derived from the name when omitted (a suffix is appended if it collides).

### Returns

- `Group = object { id, created_at, external, 7 more }`

  A zone-scoped group of users, assignable to roles and usable in policies. Roles assigned to a group are inherited by its members. `external` is false for groups managed in Keycard and true for groups synced from an external directory.

  - `id: string`

    Unique identifier of the group

  - `created_at: string`

    Entity creation timestamp

  - `external: boolean`

    Whether the group is synced from an external directory. When true the group is directory-owned and its membership is read-only; when false it is managed in Keycard. Read-only: set by external sync, never by the caller.

  - `identifier: string`

    User-specified identifier, unique within the zone. Automatically assigned for groups from an external directory.

  - `name: string`

    Human-readable group name

  - `organization_id: string`

    Organization this group belongs to

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this group belongs to

  - `member_count: optional number`

    Number of users in the group. Included only when requested via `expand[]=member_count` (group get or list).

  - `roles: optional array of string`

    Identifiers of the roles assigned to the group; members inherit them. Deduped across scopes. Included only when requested via `expand[]=roles` (group get or list).

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY" \
    -d '{
          "name": "x"
        }'
```

## Retrieve

**get** `/zones/{zoneId}/groups/{groupId}`

Returns a group by ID. Pass `expand[]=member_count` for its member count and `expand[]=roles` for the identifiers of its assigned roles.

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Query Parameters

- `"expand[]": optional "member_count" or "roles" or array of "member_count" or "roles"`

  - `UnionMember0 = "member_count" or "roles"`

    - `"member_count"`

    - `"roles"`

  - `UnionMember1 = array of "member_count" or "roles"`

    - `"member_count"`

    - `"roles"`

### Returns

- `Group = object { id, created_at, external, 7 more }`

  A zone-scoped group of users, assignable to roles and usable in policies. Roles assigned to a group are inherited by its members. `external` is false for groups managed in Keycard and true for groups synced from an external directory.

  - `id: string`

    Unique identifier of the group

  - `created_at: string`

    Entity creation timestamp

  - `external: boolean`

    Whether the group is synced from an external directory. When true the group is directory-owned and its membership is read-only; when false it is managed in Keycard. Read-only: set by external sync, never by the caller.

  - `identifier: string`

    User-specified identifier, unique within the zone. Automatically assigned for groups from an external directory.

  - `name: string`

    Human-readable group name

  - `organization_id: string`

    Organization this group belongs to

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this group belongs to

  - `member_count: optional number`

    Number of users in the group. Included only when requested via `expand[]=member_count` (group get or list).

  - `roles: optional array of string`

    Identifiers of the roles assigned to the group; members inherit them. Deduped across scopes. Included only when requested via `expand[]=roles` (group get or list).

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Update

**patch** `/zones/{zoneId}/groups/{groupId}`

Updates a group's name and/or identifier (partial update). A group's source is immutable. The name of a group synced from an external directory cannot be changed while external sync is enabled for the zone; its identifier can.

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Body Parameters

- `identifier: optional string`

  User-specified identifier, unique within the zone.

- `name: optional string`

  Human-readable group name

### Returns

- `Group = object { id, created_at, external, 7 more }`

  A zone-scoped group of users, assignable to roles and usable in policies. Roles assigned to a group are inherited by its members. `external` is false for groups managed in Keycard and true for groups synced from an external directory.

  - `id: string`

    Unique identifier of the group

  - `created_at: string`

    Entity creation timestamp

  - `external: boolean`

    Whether the group is synced from an external directory. When true the group is directory-owned and its membership is read-only; when false it is managed in Keycard. Read-only: set by external sync, never by the caller.

  - `identifier: string`

    User-specified identifier, unique within the zone. Automatically assigned for groups from an external directory.

  - `name: string`

    Human-readable group name

  - `organization_id: string`

    Organization this group belongs to

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this group belongs to

  - `member_count: optional number`

    Number of users in the group. Included only when requested via `expand[]=member_count` (group get or list).

  - `roles: optional array of string`

    Identifiers of the roles assigned to the group; members inherit them. Deduped across scopes. Included only when requested via `expand[]=roles` (group get or list).

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID \
    -X PATCH \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Delete

**delete** `/zones/{zoneId}/groups/{groupId}`

Deletes a group and its memberships and role assignments. Groups synced from an external directory can only be deleted by that directory (after external sync is disabled).

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID \
    -X DELETE \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Domain Types

### Group

- `Group = object { id, created_at, external, 7 more }`

  A zone-scoped group of users, assignable to roles and usable in policies. Roles assigned to a group are inherited by its members. `external` is false for groups managed in Keycard and true for groups synced from an external directory.

  - `id: string`

    Unique identifier of the group

  - `created_at: string`

    Entity creation timestamp

  - `external: boolean`

    Whether the group is synced from an external directory. When true the group is directory-owned and its membership is read-only; when false it is managed in Keycard. Read-only: set by external sync, never by the caller.

  - `identifier: string`

    User-specified identifier, unique within the zone. Automatically assigned for groups from an external directory.

  - `name: string`

    Human-readable group name

  - `organization_id: string`

    Organization this group belongs to

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this group belongs to

  - `member_count: optional number`

    Number of users in the group. Included only when requested via `expand[]=member_count` (group get or list).

  - `roles: optional array of string`

    Identifiers of the roles assigned to the group; members inherit them. Deduped across scopes. Included only when requested via `expand[]=roles` (group get or list).

### Group Create

- `GroupCreate = object { name, identifier }`

  Schema for creating a group in Keycard. Groups synced from an external directory are created by that directory, not through this endpoint.

  - `name: string`

    Human-readable group name

  - `identifier: optional string`

    User-specified identifier, unique within the zone. Derived from the name when omitted (a suffix is appended if it collides).

### Group Update

- `GroupUpdate = object { identifier, name }`

  Schema for updating a group.

  - `identifier: optional string`

    User-specified identifier, unique within the zone.

  - `name: optional string`

    Human-readable group name

# Members

## List

**get** `/zones/{zoneId}/groups/{groupId}/members`

Returns a paginated list of the group's members. Use cursor pagination via `after`/`before`. Pass `expand[]=user` to embed each member's full user record and `expand[]=total_count` to include the matching row count. Pass `query[]` (repeatable, 1-255 chars) to search members by their user's email or federated credential subject (substring match, OR'd across repeated values). Pass `filter[id]` (repeatable, max 100) to restrict results to a known set of members by user ID — mutually exclusive with `after`/`before` (returns 400 if combined). When `filter[id]` is set, `limit` is ignored and the response contains every requested member that exists in the group, in a single page. IDs not in the group are silently omitted.

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Query Parameters

- `after: optional string`

  Cursor for forward pagination

- `before: optional string`

  Cursor for backward pagination

- `"expand[]": optional "total_count" or "user" or array of "total_count" or "user"`

  - `UnionMember0 = "total_count" or "user"`

    - `"total_count"`

    - `"user"`

  - `UnionMember1 = array of "total_count" or "user"`

    - `"total_count"`

    - `"user"`

- `"filter[id]": optional string or array of string`

  Restrict results to the member with this user ID. Repeatable, max 100. Mutually exclusive with after/before.

  - `UnionMember0 = string`

    Restrict results to the member with this user ID. Repeatable, max 100. Mutually exclusive with after/before.

  - `UnionMember1 = array of string`

- `limit: optional number`

  Maximum number of items to return

- `"query[]": optional string or array of string`

  Search members by their user's email or federated credential subject (substring match)

  - `UnionMember0 = string`

    Search members by their user's email or federated credential subject (substring match)

  - `UnionMember1 = array of string`

### Returns

- `items: array of GroupMember`

  - `created_at: string`

    Entity creation timestamp

  - `user_id: string`

    ID of the user

  - `user: optional User`

    An authenticated user entity

    - `id: string`

      Unique identifier of the user

    - `created_at: string`

      Entity creation timestamp

    - `email: string`

      Email address of the user

    - `email_verified: boolean`

      Whether the email address has been verified

    - `identifier: string`

      Zone-scoped user identifier. Defaults to the user's Keycard ID. When the provider has user_identifier_claim configured, the value is set from that claim at user creation time.

    - `organization_id: string`

      Organization that owns this user

    - `status: "active" or "disabled"`

      Status of the user. Disabled users cannot authenticate.

      - `"active"`

      - `"disabled"`

    - `updated_at: string`

      Entity update timestamp

    - `zone_id: string`

      Zone this user belongs to

    - `authenticated_at: optional string`

      Date when the user was last authenticated

    - `credentials: optional array of object { created_at, provider_id, type, 4 more }  or object { created_at, type, updated_at }`

      Authentication credentials for this user, each carrying its identity provider for federation credentials. Populated only when `expand[]=credentials` is set on the listing endpoint.

      - `IamUserCredentialFederation = object { created_at, provider_id, type, 4 more }`

        Federation credential: the user authenticates through an identity provider.

        - `created_at: string`

          Entity creation timestamp

        - `provider_id: string`

          ID of the identity provider backing this credential. `null` when the source provider has been deleted.

        - `type: "federation"`

          - `"federation"`

        - `updated_at: string`

          Entity update timestamp

        - `issuer: optional string`

          Issuer identifier of the identity provider.

        - `provider: optional Provider`

          A Provider is a system that supplies access to Resources and allows actors (Users or Applications) to authenticate.

          - `id: string`

            Unique identifier of the provider

          - `created_at: string`

            Entity creation timestamp

          - `identifier: string`

            User specified identifier, unique within the zone

          - `name: string`

            Human-readable name

          - `organization_id: string`

            Organization that owns this provider

          - `owner_type: "platform" or "customer"`

            Who owns this provider. Platform-owned providers cannot be modified via API.

            - `"platform"`

            - `"customer"`

          - `slug: string`

            URL-safe identifier, unique within the zone

          - `updated_at: string`

            Entity update timestamp

          - `zone_id: string`

            Zone this provider belongs to

          - `client_id: optional string`

            OAuth 2.0 client identifier

          - `client_secret_set: optional boolean`

            Indicates whether a client secret is configured

          - `description: optional string`

            Human-readable description

          - `metadata: optional object { icon_url }`

            Provider metadata

            - `icon_url: optional string`

              Icon URL

          - `protocols: optional object { oauth2, openid }`

            Protocol-specific configuration

            - `oauth2: optional object { issuer, authorization_endpoint, authorization_parameters, 10 more }`

              OAuth 2.0 protocol configuration

              - `issuer: string`

                OIDC issuer URL used for discovery and token validation.

              - `authorization_endpoint: optional string`

              - `authorization_parameters: optional map[string]`

                Custom query parameters appended to authorization redirect URLs. Use for non-standard providers (e.g. Google prompt=consent, access_type=offline).

              - `authorization_resource_enabled: optional boolean`

                Whether to include the resource parameter in authorization requests.

              - `authorization_resource_parameter: optional string`

                The resource parameter value to include in authorization requests. Defaults to "resource" when authorization_resource_enabled is true.

              - `code_challenge_methods_supported: optional array of string`

              - `jwks_uri: optional string`

              - `registration_endpoint: optional string`

              - `scope_parameter: optional string`

                The query parameter name for scopes in authorization requests. Defaults to "scope". Slack v2 uses "user_scope".

              - `scope_separator: optional string`

                The separator character for scope values. Defaults to " " (space). Slack v2 uses ",".

              - `scopes_supported: optional array of string`

              - `token_endpoint: optional string`

              - `token_response_access_token_pointer: optional string`

                Dot-separated path to the access token in the token response body. Defaults to "access_token". Slack v2 uses "authed_user.access_token".

            - `openid: optional object { external_id_claim, scopes, single_logout_enabled, 2 more }`

              OpenID Connect protocol configuration

              - `external_id_claim: optional string`

                Name of the OIDC claim carrying the stable external id used to correlate logins with externally provisioned (SCIM) users. Defaults to "sub". Set to "oid" for Entra, whose pairwise "sub" differs from the SCIM externalId.

              - `scopes: optional array of string`

                Additional OIDC scopes to request from this provider during authentication (e.g. "groups"). Merged with the default scopes (openid, profile, email).

              - `single_logout_enabled: optional boolean`

                When true, logging out of the zone propagates the logout to this provider's end_session_endpoint (RP-initiated logout). Defaults to false.

              - `user_identifier_claim: optional string`

                Name of a top-level string claim in this provider's ID Token to use as the user identifier on user creation. When not set, the user's Keycard ID is used.

              - `userinfo_endpoint: optional string`

          - `type: optional "external" or "keycard-vault" or "keycard-sts"`

            - `"external"`

            - `"keycard-vault"`

            - `"keycard-sts"`

        - `subject: optional string`

          Subject identifier from the identity provider.

      - `IamUserCredentialPassword = object { created_at, type, updated_at }`

        Password credential: the user authenticates with email and password. The email lives on the user.

        - `created_at: string`

          Entity creation timestamp

        - `type: "password"`

          - `"password"`

        - `updated_at: string`

          Entity update timestamp

    - `grant_count: optional number`

      Delegated-grant count for this user. Populated only when `expand[]=grant_count` is set on the listing endpoint.

    - `groups: optional array of object { id, identifier, name }`

      Groups this user belongs to within the zone. Populated only when `expand[]=groups` is set on the listing endpoint.

      - `id: string`

        Unique identifier of the group

      - `identifier: string`

        Zone-unique slug that policy rules match on.

      - `name: string`

        Human-readable group name

    - `issuer: optional string`

      Issuer identifier of the identity provider

    - `provider_id: optional string`

      Reference to the identity provider. This field is undefined when the source identity provider is deleted but the user is not deleted.

    - `role_assignments: optional array of object { role_id, role_identifier, role_owner_type, 3 more }`

      Role grants for this user within the zone. Populated only when `expand[]=role-assignments` is set on the listing endpoint.

      - `role_id: string`

        ID of the assigned role

      - `role_identifier: string`

        Role identifier: a lowercase slug (letters and digits separated by single hyphens or underscores), unique per owner type within a zone. Role identifiers surface in policy evaluation, so the slug restriction keeps them unambiguous in policy text.

      - `role_owner_type: "platform" or "customer"`

        Owner type of the granted role. Disambiguates roles that share an identifier across owner types.

        - `"platform"`

        - `"customer"`

      - `scope: object { id, type }`

        The resource this grant is scoped to, or null when the grant is unscoped (applies to the owning zone itself).

        - `id: string`

          The ID of the scoped resource.

        - `type: string`

          The kind of resource this grant is scoped to (e.g. `zone`).

      - `source: "user" or "group"`

        The principal that holds this grant: `user` when assigned directly to the user, or `group` when inherited through group membership.

        - `"user"`

        - `"group"`

      - `group_id: optional string`

        ID of the group this grant is inherited from. Present only when `source` is `group`.

    - `session_count: optional number`

      Session count for this user. Populated only when `expand[]=session_count` is set on the listing endpoint.

    - `subject: optional string`

      Subject identifier from the identity provider

- `pagination: object { after_cursor, before_cursor, total_count }`

  Cursor-based pagination metadata

  - `after_cursor: string`

    An opaque cursor used for paginating through a list of results

  - `before_cursor: string`

    An opaque cursor used for paginating through a list of results

  - `total_count: optional number`

    Total number of items matching the query. Only included when expand[]=total_count is requested.

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID/members \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Add

**post** `/zones/{zoneId}/groups/{groupId}/members`

Adds a user to a group managed in Keycard. Membership of externally synced groups is not managed manually.

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Body Parameters

- `user_id: string`

  ID of the user to add to the group

### Returns

- `GroupMember = object { created_at, user_id, user }`

  A user's membership in a group

  - `created_at: string`

    Entity creation timestamp

  - `user_id: string`

    ID of the user

  - `user: optional User`

    An authenticated user entity

    - `id: string`

      Unique identifier of the user

    - `created_at: string`

      Entity creation timestamp

    - `email: string`

      Email address of the user

    - `email_verified: boolean`

      Whether the email address has been verified

    - `identifier: string`

      Zone-scoped user identifier. Defaults to the user's Keycard ID. When the provider has user_identifier_claim configured, the value is set from that claim at user creation time.

    - `organization_id: string`

      Organization that owns this user

    - `status: "active" or "disabled"`

      Status of the user. Disabled users cannot authenticate.

      - `"active"`

      - `"disabled"`

    - `updated_at: string`

      Entity update timestamp

    - `zone_id: string`

      Zone this user belongs to

    - `authenticated_at: optional string`

      Date when the user was last authenticated

    - `credentials: optional array of object { created_at, provider_id, type, 4 more }  or object { created_at, type, updated_at }`

      Authentication credentials for this user, each carrying its identity provider for federation credentials. Populated only when `expand[]=credentials` is set on the listing endpoint.

      - `IamUserCredentialFederation = object { created_at, provider_id, type, 4 more }`

        Federation credential: the user authenticates through an identity provider.

        - `created_at: string`

          Entity creation timestamp

        - `provider_id: string`

          ID of the identity provider backing this credential. `null` when the source provider has been deleted.

        - `type: "federation"`

          - `"federation"`

        - `updated_at: string`

          Entity update timestamp

        - `issuer: optional string`

          Issuer identifier of the identity provider.

        - `provider: optional Provider`

          A Provider is a system that supplies access to Resources and allows actors (Users or Applications) to authenticate.

          - `id: string`

            Unique identifier of the provider

          - `created_at: string`

            Entity creation timestamp

          - `identifier: string`

            User specified identifier, unique within the zone

          - `name: string`

            Human-readable name

          - `organization_id: string`

            Organization that owns this provider

          - `owner_type: "platform" or "customer"`

            Who owns this provider. Platform-owned providers cannot be modified via API.

            - `"platform"`

            - `"customer"`

          - `slug: string`

            URL-safe identifier, unique within the zone

          - `updated_at: string`

            Entity update timestamp

          - `zone_id: string`

            Zone this provider belongs to

          - `client_id: optional string`

            OAuth 2.0 client identifier

          - `client_secret_set: optional boolean`

            Indicates whether a client secret is configured

          - `description: optional string`

            Human-readable description

          - `metadata: optional object { icon_url }`

            Provider metadata

            - `icon_url: optional string`

              Icon URL

          - `protocols: optional object { oauth2, openid }`

            Protocol-specific configuration

            - `oauth2: optional object { issuer, authorization_endpoint, authorization_parameters, 10 more }`

              OAuth 2.0 protocol configuration

              - `issuer: string`

                OIDC issuer URL used for discovery and token validation.

              - `authorization_endpoint: optional string`

              - `authorization_parameters: optional map[string]`

                Custom query parameters appended to authorization redirect URLs. Use for non-standard providers (e.g. Google prompt=consent, access_type=offline).

              - `authorization_resource_enabled: optional boolean`

                Whether to include the resource parameter in authorization requests.

              - `authorization_resource_parameter: optional string`

                The resource parameter value to include in authorization requests. Defaults to "resource" when authorization_resource_enabled is true.

              - `code_challenge_methods_supported: optional array of string`

              - `jwks_uri: optional string`

              - `registration_endpoint: optional string`

              - `scope_parameter: optional string`

                The query parameter name for scopes in authorization requests. Defaults to "scope". Slack v2 uses "user_scope".

              - `scope_separator: optional string`

                The separator character for scope values. Defaults to " " (space). Slack v2 uses ",".

              - `scopes_supported: optional array of string`

              - `token_endpoint: optional string`

              - `token_response_access_token_pointer: optional string`

                Dot-separated path to the access token in the token response body. Defaults to "access_token". Slack v2 uses "authed_user.access_token".

            - `openid: optional object { external_id_claim, scopes, single_logout_enabled, 2 more }`

              OpenID Connect protocol configuration

              - `external_id_claim: optional string`

                Name of the OIDC claim carrying the stable external id used to correlate logins with externally provisioned (SCIM) users. Defaults to "sub". Set to "oid" for Entra, whose pairwise "sub" differs from the SCIM externalId.

              - `scopes: optional array of string`

                Additional OIDC scopes to request from this provider during authentication (e.g. "groups"). Merged with the default scopes (openid, profile, email).

              - `single_logout_enabled: optional boolean`

                When true, logging out of the zone propagates the logout to this provider's end_session_endpoint (RP-initiated logout). Defaults to false.

              - `user_identifier_claim: optional string`

                Name of a top-level string claim in this provider's ID Token to use as the user identifier on user creation. When not set, the user's Keycard ID is used.

              - `userinfo_endpoint: optional string`

          - `type: optional "external" or "keycard-vault" or "keycard-sts"`

            - `"external"`

            - `"keycard-vault"`

            - `"keycard-sts"`

        - `subject: optional string`

          Subject identifier from the identity provider.

      - `IamUserCredentialPassword = object { created_at, type, updated_at }`

        Password credential: the user authenticates with email and password. The email lives on the user.

        - `created_at: string`

          Entity creation timestamp

        - `type: "password"`

          - `"password"`

        - `updated_at: string`

          Entity update timestamp

    - `grant_count: optional number`

      Delegated-grant count for this user. Populated only when `expand[]=grant_count` is set on the listing endpoint.

    - `groups: optional array of object { id, identifier, name }`

      Groups this user belongs to within the zone. Populated only when `expand[]=groups` is set on the listing endpoint.

      - `id: string`

        Unique identifier of the group

      - `identifier: string`

        Zone-unique slug that policy rules match on.

      - `name: string`

        Human-readable group name

    - `issuer: optional string`

      Issuer identifier of the identity provider

    - `provider_id: optional string`

      Reference to the identity provider. This field is undefined when the source identity provider is deleted but the user is not deleted.

    - `role_assignments: optional array of object { role_id, role_identifier, role_owner_type, 3 more }`

      Role grants for this user within the zone. Populated only when `expand[]=role-assignments` is set on the listing endpoint.

      - `role_id: string`

        ID of the assigned role

      - `role_identifier: string`

        Role identifier: a lowercase slug (letters and digits separated by single hyphens or underscores), unique per owner type within a zone. Role identifiers surface in policy evaluation, so the slug restriction keeps them unambiguous in policy text.

      - `role_owner_type: "platform" or "customer"`

        Owner type of the granted role. Disambiguates roles that share an identifier across owner types.

        - `"platform"`

        - `"customer"`

      - `scope: object { id, type }`

        The resource this grant is scoped to, or null when the grant is unscoped (applies to the owning zone itself).

        - `id: string`

          The ID of the scoped resource.

        - `type: string`

          The kind of resource this grant is scoped to (e.g. `zone`).

      - `source: "user" or "group"`

        The principal that holds this grant: `user` when assigned directly to the user, or `group` when inherited through group membership.

        - `"user"`

        - `"group"`

      - `group_id: optional string`

        ID of the group this grant is inherited from. Present only when `source` is `group`.

    - `session_count: optional number`

      Session count for this user. Populated only when `expand[]=session_count` is set on the listing endpoint.

    - `subject: optional string`

      Subject identifier from the identity provider

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID/members \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY" \
    -d '{
          "user_id": "user_id"
        }'
```

## Remove

**delete** `/zones/{zoneId}/groups/{groupId}/members/{userId}`

Removes a user from a group managed in Keycard. Membership of externally synced groups is not managed manually. A member is identified by its user's ID.

### Path Parameters

- `zoneId: string`

- `groupId: string`

- `userId: string`

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID/members/$USER_ID \
    -X DELETE \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Domain Types

### Group Member

- `GroupMember = object { created_at, user_id, user }`

  A user's membership in a group

  - `created_at: string`

    Entity creation timestamp

  - `user_id: string`

    ID of the user

  - `user: optional User`

    An authenticated user entity

    - `id: string`

      Unique identifier of the user

    - `created_at: string`

      Entity creation timestamp

    - `email: string`

      Email address of the user

    - `email_verified: boolean`

      Whether the email address has been verified

    - `identifier: string`

      Zone-scoped user identifier. Defaults to the user's Keycard ID. When the provider has user_identifier_claim configured, the value is set from that claim at user creation time.

    - `organization_id: string`

      Organization that owns this user

    - `status: "active" or "disabled"`

      Status of the user. Disabled users cannot authenticate.

      - `"active"`

      - `"disabled"`

    - `updated_at: string`

      Entity update timestamp

    - `zone_id: string`

      Zone this user belongs to

    - `authenticated_at: optional string`

      Date when the user was last authenticated

    - `credentials: optional array of object { created_at, provider_id, type, 4 more }  or object { created_at, type, updated_at }`

      Authentication credentials for this user, each carrying its identity provider for federation credentials. Populated only when `expand[]=credentials` is set on the listing endpoint.

      - `IamUserCredentialFederation = object { created_at, provider_id, type, 4 more }`

        Federation credential: the user authenticates through an identity provider.

        - `created_at: string`

          Entity creation timestamp

        - `provider_id: string`

          ID of the identity provider backing this credential. `null` when the source provider has been deleted.

        - `type: "federation"`

          - `"federation"`

        - `updated_at: string`

          Entity update timestamp

        - `issuer: optional string`

          Issuer identifier of the identity provider.

        - `provider: optional Provider`

          A Provider is a system that supplies access to Resources and allows actors (Users or Applications) to authenticate.

          - `id: string`

            Unique identifier of the provider

          - `created_at: string`

            Entity creation timestamp

          - `identifier: string`

            User specified identifier, unique within the zone

          - `name: string`

            Human-readable name

          - `organization_id: string`

            Organization that owns this provider

          - `owner_type: "platform" or "customer"`

            Who owns this provider. Platform-owned providers cannot be modified via API.

            - `"platform"`

            - `"customer"`

          - `slug: string`

            URL-safe identifier, unique within the zone

          - `updated_at: string`

            Entity update timestamp

          - `zone_id: string`

            Zone this provider belongs to

          - `client_id: optional string`

            OAuth 2.0 client identifier

          - `client_secret_set: optional boolean`

            Indicates whether a client secret is configured

          - `description: optional string`

            Human-readable description

          - `metadata: optional object { icon_url }`

            Provider metadata

            - `icon_url: optional string`

              Icon URL

          - `protocols: optional object { oauth2, openid }`

            Protocol-specific configuration

            - `oauth2: optional object { issuer, authorization_endpoint, authorization_parameters, 10 more }`

              OAuth 2.0 protocol configuration

              - `issuer: string`

                OIDC issuer URL used for discovery and token validation.

              - `authorization_endpoint: optional string`

              - `authorization_parameters: optional map[string]`

                Custom query parameters appended to authorization redirect URLs. Use for non-standard providers (e.g. Google prompt=consent, access_type=offline).

              - `authorization_resource_enabled: optional boolean`

                Whether to include the resource parameter in authorization requests.

              - `authorization_resource_parameter: optional string`

                The resource parameter value to include in authorization requests. Defaults to "resource" when authorization_resource_enabled is true.

              - `code_challenge_methods_supported: optional array of string`

              - `jwks_uri: optional string`

              - `registration_endpoint: optional string`

              - `scope_parameter: optional string`

                The query parameter name for scopes in authorization requests. Defaults to "scope". Slack v2 uses "user_scope".

              - `scope_separator: optional string`

                The separator character for scope values. Defaults to " " (space). Slack v2 uses ",".

              - `scopes_supported: optional array of string`

              - `token_endpoint: optional string`

              - `token_response_access_token_pointer: optional string`

                Dot-separated path to the access token in the token response body. Defaults to "access_token". Slack v2 uses "authed_user.access_token".

            - `openid: optional object { external_id_claim, scopes, single_logout_enabled, 2 more }`

              OpenID Connect protocol configuration

              - `external_id_claim: optional string`

                Name of the OIDC claim carrying the stable external id used to correlate logins with externally provisioned (SCIM) users. Defaults to "sub". Set to "oid" for Entra, whose pairwise "sub" differs from the SCIM externalId.

              - `scopes: optional array of string`

                Additional OIDC scopes to request from this provider during authentication (e.g. "groups"). Merged with the default scopes (openid, profile, email).

              - `single_logout_enabled: optional boolean`

                When true, logging out of the zone propagates the logout to this provider's end_session_endpoint (RP-initiated logout). Defaults to false.

              - `user_identifier_claim: optional string`

                Name of a top-level string claim in this provider's ID Token to use as the user identifier on user creation. When not set, the user's Keycard ID is used.

              - `userinfo_endpoint: optional string`

          - `type: optional "external" or "keycard-vault" or "keycard-sts"`

            - `"external"`

            - `"keycard-vault"`

            - `"keycard-sts"`

        - `subject: optional string`

          Subject identifier from the identity provider.

      - `IamUserCredentialPassword = object { created_at, type, updated_at }`

        Password credential: the user authenticates with email and password. The email lives on the user.

        - `created_at: string`

          Entity creation timestamp

        - `type: "password"`

          - `"password"`

        - `updated_at: string`

          Entity update timestamp

    - `grant_count: optional number`

      Delegated-grant count for this user. Populated only when `expand[]=grant_count` is set on the listing endpoint.

    - `groups: optional array of object { id, identifier, name }`

      Groups this user belongs to within the zone. Populated only when `expand[]=groups` is set on the listing endpoint.

      - `id: string`

        Unique identifier of the group

      - `identifier: string`

        Zone-unique slug that policy rules match on.

      - `name: string`

        Human-readable group name

    - `issuer: optional string`

      Issuer identifier of the identity provider

    - `provider_id: optional string`

      Reference to the identity provider. This field is undefined when the source identity provider is deleted but the user is not deleted.

    - `role_assignments: optional array of object { role_id, role_identifier, role_owner_type, 3 more }`

      Role grants for this user within the zone. Populated only when `expand[]=role-assignments` is set on the listing endpoint.

      - `role_id: string`

        ID of the assigned role

      - `role_identifier: string`

        Role identifier: a lowercase slug (letters and digits separated by single hyphens or underscores), unique per owner type within a zone. Role identifiers surface in policy evaluation, so the slug restriction keeps them unambiguous in policy text.

      - `role_owner_type: "platform" or "customer"`

        Owner type of the granted role. Disambiguates roles that share an identifier across owner types.

        - `"platform"`

        - `"customer"`

      - `scope: object { id, type }`

        The resource this grant is scoped to, or null when the grant is unscoped (applies to the owning zone itself).

        - `id: string`

          The ID of the scoped resource.

        - `type: string`

          The kind of resource this grant is scoped to (e.g. `zone`).

      - `source: "user" or "group"`

        The principal that holds this grant: `user` when assigned directly to the user, or `group` when inherited through group membership.

        - `"user"`

        - `"group"`

      - `group_id: optional string`

        ID of the group this grant is inherited from. Present only when `source` is `group`.

    - `session_count: optional number`

      Session count for this user. Populated only when `expand[]=session_count` is set on the listing endpoint.

    - `subject: optional string`

      Subject identifier from the identity provider

### Group Member Create

- `GroupMemberCreate = object { user_id }`

  Schema for adding a user to a group

  - `user_id: string`

    ID of the user to add to the group

# Roles

## List

**get** `/zones/{zoneId}/groups/{groupId}/roles`

Returns the roles assigned to the group. Members inherit these roles. Returns the shared role-assignment shape with `principal_type` set to `group`. Use cursor pagination via `after`/`before`; pass `expand[]=total_count` to include the matching row count. Pass `filter[id]` (repeatable, max 100) to restrict results to a known set of role assignments, mutually exclusive with `after`/`before` (returns 400 if combined). When `filter[id]` is set, `limit` is ignored and the response contains every requested assignment that exists on the group, in a single page. IDs not on the group are silently omitted.

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Query Parameters

- `after: optional string`

  Cursor for forward pagination

- `before: optional string`

  Cursor for backward pagination

- `"expand[]": optional "total_count" or array of "total_count"`

  - `UnionMember0 = "total_count"`

    - `"total_count"`

  - `UnionMember1 = array of "total_count"`

    - `"total_count"`

- `"filter[id]": optional string or array of string`

  Restrict results to the role assignment with this ID. Repeatable, max 100. Mutually exclusive with after/before.

  - `UnionMember0 = string`

    Restrict results to the role assignment with this ID. Repeatable, max 100. Mutually exclusive with after/before.

  - `UnionMember1 = array of string`

- `limit: optional number`

  Maximum number of items to return

### Returns

- `items: array of RoleAssignment`

  - `id: string`

    Unique identifier of the role assignment

  - `created_at: string`

    Entity creation timestamp

  - `principal_id: string`

    ID of the principal the role is assigned to (a user, application, or group ID).

  - `principal_type: string`

    The kind of principal the role is assigned to: `user`, `application`, or `group`. A role assigned to a `group` is inherited by that group's members.

  - `role_id: string`

    ID of the assigned role

  - `role_identifier: string`

    Role identifier: a lowercase slug (letters and digits separated by single hyphens or underscores), unique per owner type within a zone. Role identifiers surface in policy evaluation, so the slug restriction keeps them unambiguous in policy text.

  - `role_owner_type: "platform" or "customer"`

    Owner type of the assigned role. Disambiguates roles that share an identifier across owner types.

    - `"platform"`

    - `"customer"`

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this assignment belongs to

  - `scope_id: optional string`

    The ID of the scoped resource. Null when the assignment is unscoped.

  - `scope_type: optional string`

    The kind of resource this grant is scoped to (e.g. `zone`). Null when the assignment is unscoped (applies to the owning zone itself).

- `pagination: object { after_cursor, before_cursor, total_count }`

  Cursor-based pagination metadata

  - `after_cursor: string`

    An opaque cursor used for paginating through a list of results

  - `before_cursor: string`

    An opaque cursor used for paginating through a list of results

  - `total_count: optional number`

    Total number of items matching the query. Only included when expand[]=total_count is requested.

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID/roles \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Add

**post** `/zones/{zoneId}/groups/{groupId}/roles`

Assigns a role to the group; members inherit it. Provide role_id, or role_identifier with owner_type. Returns the shared role-assignment shape with `principal_type` set to `group`.

### Path Parameters

- `zoneId: string`

- `groupId: string`

### Body Parameters

- `owner_type: optional "platform" or "customer"`

  Owner type of the role to assign. Required with role_identifier (an identifier is unique only per owner type); must be omitted with role_id.

  - `"platform"`

  - `"customer"`

- `role_id: optional string`

  ID of the role to assign. Provide exactly one of role_id or role_identifier; owner_type must be omitted when role_id is used.

- `role_identifier: optional string`

  Role identifier: a lowercase slug (letters and digits separated by single hyphens or underscores), unique per owner type within a zone. Role identifiers surface in policy evaluation, so the slug restriction keeps them unambiguous in policy text.

- `scope_id: optional string`

  The ID of the resource to scope the grant to. Provide together with scope_type, or omit both for an unscoped assignment. When scope_type is `zone`, this must reference a different zone in the same organization.

- `scope_type: optional string`

  The kind of resource to scope the grant to (e.g. `zone`). Provide together with scope_id, or omit both for an unscoped assignment (applies to the owning zone itself). Only platform roles on the org zone may carry a scope.

### Returns

- `RoleAssignment = object { id, created_at, principal_id, 8 more }`

  Represents a role assigned to a principal within a zone

  - `id: string`

    Unique identifier of the role assignment

  - `created_at: string`

    Entity creation timestamp

  - `principal_id: string`

    ID of the principal the role is assigned to (a user, application, or group ID).

  - `principal_type: string`

    The kind of principal the role is assigned to: `user`, `application`, or `group`. A role assigned to a `group` is inherited by that group's members.

  - `role_id: string`

    ID of the assigned role

  - `role_identifier: string`

    Role identifier: a lowercase slug (letters and digits separated by single hyphens or underscores), unique per owner type within a zone. Role identifiers surface in policy evaluation, so the slug restriction keeps them unambiguous in policy text.

  - `role_owner_type: "platform" or "customer"`

    Owner type of the assigned role. Disambiguates roles that share an identifier across owner types.

    - `"platform"`

    - `"customer"`

  - `updated_at: string`

    Entity update timestamp

  - `zone_id: string`

    Zone this assignment belongs to

  - `scope_id: optional string`

    The ID of the scoped resource. Null when the assignment is unscoped.

  - `scope_type: optional string`

    The kind of resource this grant is scoped to (e.g. `zone`). Null when the assignment is unscoped (applies to the owning zone itself).

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID/roles \
    -X POST \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```

## Remove

**delete** `/zones/{zoneId}/groups/{groupId}/roles/{roleId}`

Revokes a role from the group. Provide the same (scope_type, scope_id) pair the grant was created with, or omit both to revoke the unscoped grant.

### Path Parameters

- `zoneId: string`

- `groupId: string`

- `roleId: string`

### Query Parameters

- `scope_id: optional string`

  Scope target of the grant to revoke. Provide together with scope_type.

- `scope_type: optional string`

  Scope kind of the grant to revoke. Provide together with scope_id.

### Example

```http
curl https://api.keycard.ai/zones/$ZONE_ID/groups/$GROUP_ID/roles/$ROLE_ID \
    -X DELETE \
    -H "Authorization: Bearer $KEYCARD_API_API_KEY"
```
