---
title: "Add members in bulk"
method: POST
path: "/groups/{id}/members/bulk"
tags: ["groups"]
---

# Add members in bulk

`POST /groups/{id}/members/bulk`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/groups/{id}/members/bulk`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

Adds many accounts to one group in a single call, reporting EVERY id's outcome rather than aborting on the first problem: `added`, `already_member` (membership is a set, so re-running an import is a no-op, not an error), `not_found`, or `cross_tenant`. Authorization is the same ResourceAuth write on the GROUP that the single-member POST runs — there is no per-account permission to add somebody to a group you may already write — so the ids in the body are data, not resources. A cross-tenant account is a row outcome here rather than the whole-request 403 the single-member path answers, because one bad line must not discard a correct import; the invariant is unchanged and such an account is never added. Duplicate ids within one call collapse, and the second mention reports already_member. One membership event and one audit record are written per member actually added, exactly as the single-member path writes them. role (member | manager, default member) applies to every ADDED member; existing members keep theirs; any other role is 400 invalid_role. 409 group_archived on an archived group. At most 1000 ids per call.

## Path parameters

- `id` string, required

## Request body

- InternalDeliveryHttpHandlerBulkMembersRequest
  - `account_ids` string[]
  - `role` 'member' | 'manager': Role every ADDED member joins in (member, the default, or manager). Ignored by bulk-remove; existing members keep their role.

## Response `200`

OK

- InternalDeliveryHttpHandlerBulkMembersResponse
  - `data` EnforcerV3InternalUsecaseGroupsMemberBulkResult
    - `added` integer
    - `agent_consent_required` integer
    - `already_member` integer
    - `cross_tenant` integer
    - `moved` integer
    - `not_found` integer
    - `not_group_member` integer
    - `not_member` integer
    - `removed` integer
    - `results` EnforcerV3InternalUsecaseGroupsMemberOutcome[]
      - `account_id` string
      - `status` string: Status is one of: added they are in the group, and were not before already_member they were already in it — an import re-run, not an error not_found no such account cross_tenant the account belongs to a different tenant agent_consent_required the account is a service account (AGENTS_CONTRACT §6.5) and the caller is not a manager() of its bound agent (or TenantManage, for a plain service account with no agent) not_group_member the account is a service account and the caller — short of TenantManage/CrossTenant — is not themselves a member of this group
  - `success` boolean

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden

---

[API](https://skmtc.dev/instruxi/apis/enforcer-api.md) · [All operations](https://skmtc.dev/instruxi/apis/enforcer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/instruxi/apis/enforcer-api/revisions/fa82bc1becc0?raw)
