# 以编程方式管理用户访问控制

本指南介绍了如何通过 Hub API 管理组织成员角色和资源组成员身份：更改成员的组织角色和资源组分配、列出资源组、将用户添加到组以及批量工作流。

**目录：**

- [通过 API 更改成员角色](#change-member-role-via-api) — 设置成员的 org 角色和资源组分配（每个请求对应一个成员）。
- [资源组 API](#resource-groups-api) — 列出资源组并向其中添加用户。
- [通过 API 配置自动加入](#configure-auto-join-via-api) — 在资源组上启用或禁用自动加入。
- [通过 API 邀请外部协作者](#invite-external-collaborators-via-api) — 将组织外的用户邀请到资源组中。
- [通过 API 更改成员账户类型](#change-member-account-type-via-api) — 将成员转换为外部协作者，或恢复原状。

---

## 通过 API 更改成员角色

您可以使用 Hub API 更改成员的 **组织角色**（无访问权限 / 只读 / 贡献者 / 写入 / 管理员），并可选择更改其在 **资源组** 中的角色。该 API 每次请求仅更新 **一名成员**。若要为多名成员更改角色，请在循环中调用该 API（示例见下文）。

**OpenAPI 参考：**PUT /api/organizations/&#123;name&#125;/members/&#123;username&#125;/role

### 先决条件

- 您的组织必须拥有**订阅计划**（例如 Team 或 Enterprise）。否则，该端点将返回 402。
- 您必须以具有组织**Write**（或 Admin）权限的组织成员身份进行身份验证。
- 目标用户必须已经是该组织的**成员**。

### 基础 URL 和身份验证

- **基础 URL：** `https://huggingface.co`
- **身份验证：**在请求头中发送您的令牌：
  ```http
  Authorization: Bearer <your_access_token>
  ```
  在 [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) 创建一个细粒度令牌，其权限为“对组织设置/成员管理的写入访问权限”，并将作用域限定为您的组织。

### 更改成员角色端点

**请求**

```http
PUT /api/organizations/{org_name}/members/{username}/role
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "role": "read",
  "resourceGroups": []
}
```

- **路径参数**
  - `org_name`：组织标识符（例如 `my-org`）。
  - `username`：您要更改角色的成员的 Hugging Face **username**。
- **正文**
  - `role`（必填）：该成员的**组织级别**角色。可选值之一：`"no_access"`、`"read"`、`"contributor"`、`"write"` 或 `"admin"`。
  - `resourceGroups`（可选）：此用户的资源组分配数组。每个条目：
    - `id`：资源组 ID（24 位十六进制字符串；从[资源组列表 API](#list-resource-groups)获取 ID）。
    - `role`：该资源组中的角色：`"read"`、`"contributor"`、`"write"`、`"admin"` 或 `"inherit"`。
  - 如果省略 `resourceGroups` 或传入 `[]`，该用户将从所有资源组中移除。如果只想更改组织角色而保持资源组不变，请传入其当前的资源组成员身份（请求体始终会同时设置组织角色和资源组列表）。

**示例（curl）– 将组织角色设置为“read”，不设置资源组（移除用户之前所在的任何资源组）**

```bash
curl -s -X PUT \
  -H "Authorization: Bearer $HF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"role":"read","resourceGroups":[]}' \
  "https://huggingface.co/api/organizations/my-org/members/member1/role"
```

**示例（curl）– 设置组织角色和资源组角色（覆盖当前所有组）**

```bash
curl -s -X PUT \
  -H "Authorization: Bearer $HF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"role":"write","resourceGroups":[{"id":"507f1f77bcf86cd799439011","role":"read"}]}' \
  "https://huggingface.co/api/organizations/my-org/members/member2/role"
```

**成功响应：**状态 `200 OK`；响应体：`{ "success": true }`。

**常见错误**

- `400` — 无效的主体（例如无效的 role 或 resource group `id`）。
- `402` — 该组织没有订阅计划。
- `403` — 不允许（例如，您没有该组织的 Write 权限，或者资源组不属于该组织）。
- `404` — 未找到组织或用户。

### 更新多个成员

API 每次请求仅更改**一名成员**。没有批量端点。若要更新多名成员，请针对每个用户名调用一次该端点（例如，从列表或 CSV 中读取）。

**示例：Bash – 遍历用户名，所有用户角色相同**

```bash
ORG_NAME="my-org"
ROLE="read"
for username in member1 member2 member3 member4; do
  echo "Setting $username to $ROLE ..."
  curl -s -w "\n%{http_code}" -X PUT \
    -H "Authorization: Bearer $HF_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"role\":\"$ROLE\",\"resourceGroups\":[]}" \
    "https://huggingface.co/api/organizations/$ORG_NAME/members/$username/role"
  echo ""
done
```

**示例：Python – 遍历用户名**

```python
import os
import requests

BASE_URL = "https://huggingface.co"
HF_TOKEN = os.environ.get("HF_TOKEN", "")

def change_member_role(org_name: str, username: str, role: str, resource_groups: list | None = None):
    payload = {"role": role, "resourceGroups": resource_groups or []}
    r = requests.put(
        f"{BASE_URL}/api/organizations/{org_name}/members/{username}/role",
        headers={"Authorization": f"Bearer {HF_TOKEN}", "Content-Type": "application/json"},
        json=payload,
    )
    if r.status_code != 200:
        raise RuntimeError(f"{r.status_code}: {r.text}")
    return r.json()

org_name = "my-org"
role = "read"
for username in ["member1", "member2", "member3", "member4"]:
    print(f"Setting {username} to {role} ... ", end="")
    try:
        change_member_role(org_name, username, role)
        print("OK")
    except Exception as e:
        print(f"Failed: {e}")
```

对于每个用户的不同角色，请遍历`(username, role)`对（例如来自 CSV），并为每一对调用`change_member_role`。

---

## 资源组 API

以下端点可用于**列出**资源组并向其中**添加**用户。若要**更改**现有成员的组织级角色或其资源组分配，请参阅上文中的[通过 API 更改成员角色](#change-member-role-via-api)。

**OpenAPI 参考：** [资源组](https://huggingface.co/spaces/huggingface/openapi#tag/resource-groups)

**目录 — API 方法：**

| 目标                                               | 章节                                                                 |
| -------------------------------------------------- | ----------------------------------------------------------------------- |
| 将许多用户添加到**一个**资源组           | [将用户添加到资源组](#add-users-to-a-resource-group)         |
| 将**相同**的用户添加到**多个**资源组 | [通过循环调用 API 批量添加](#batch-add-by-looping-over-the-api) |
| 为每个组添加**不同的**用户                  | [通过循环调用 API 批量添加](#batch-add-by-looping-over-the-api) |

### 基础 URL 和身份验证

- **基础 URL：** `https://huggingface.co`
- **身份验证：**使用以下其中一种：
  - **访问令牌（脚本推荐）：**在 [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) 创建一个细粒度令牌，权限范围为“对组织设置/成员管理的写入访问”，并将其限定在你的组织内。在请求头中发送该令牌：
    ```http
    Authorization: Bearer <your_access_token>
    ```
  - **会话 Cookie：**如果从与 Hub UI 共享同一会话的浏览器或工具发起调用，Cookie 会自动发送。

### 列出资源组

获取您可以为该组织管理的所有资源组。使用此操作获取每个组的 `id`，以便在 add-users 调用中使用。

**请求**

```http
GET /api/organizations/{org_name}/resource-groups
Authorization: Bearer <your_access_token>
```

**示例（curl）**

```bash
curl -s -H "Authorization: Bearer $HF_TOKEN" \
  "https://huggingface.co/api/organizations/my-org/resource-groups"
```

**示例响应（已截断）**

```json
[
  {
    "id": "507f1f77bcf86cd799439011",
    "name": "Cohort 2024",
    "description": "Members in this group",
    "users": [...],
    "repos": [...]
  }
]
```

在添加用户时，请使用每个资源组的 `id`。

### 将用户添加到资源组

在单个请求中向单个资源组添加一个或多个用户。您可以在同一请求中发送多个用户。

**请求**

```http
POST /api/organizations/{org_name}/resource-groups/{resource_group_id}/users
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "users": [
    { "user": "member1", "role": "read" },
    { "user": "member2", "role": "read" },
    { "user": "member3", "role": "write" }
  ]
}
```

- **路径参数**
  - `org_name`：组织标识符（例如 `my-org`）。
  - `resource_group_id`：资源组的 `id`（来自列表端点的 24 位十六进制字符串）。
- **正文**
  - `users`：对象数组。每个对象必须包含：
    - `user`：Hugging Face **用户名**（必填）。
    - `role`：`"read"`、`"contributor"`、`"write"`、`"admin"` 或 `"inherit"` 之一。

**示例（curl）**

```bash
curl -s -X POST \
  -H "Authorization: Bearer $HF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"users":[{"user":"member1","role":"read"},{"user":"member2","role":"read"}]}' \
  "https://huggingface.co/api/organizations/my-org/resource-groups/507f1f77bcf86cd799439011/users"
```

**成功：**状态为 `200 OK`；响应体为更新后的资源组对象（`users` 中包含新增的用户）。

**常见错误：**

- `400` — 例如，用户未找到、用户名重复或请求体无效。
- `403` — 不允许（例如，用户不在组织中，或已在资源组中）。消息将指示用户是未加入组织还是已在组中。

### 通过电子邮件添加成员（变通方法）

add-users 端点仅接受 **Hugging Face 用户名**，不接受电子邮件地址。如果您有一份 **电子邮件地址** 列表（例如成员邮箱），可以先将电子邮件解析为用户名，然后再调用 add-users API。

请注意，邮件过滤**仅**在邮件域名与组织的允许域名之一匹配时才有效：即**组织邮件域名**（设置 → 账户 → 组织邮件域名）和/或该组织的**SSO 允许域名**（如果已配置 SSO）。

**步骤 1 – 将电子邮件解析为用户名**

```http
GET /api/organizations/{org_name}/members?email={email}&limit=1
Authorization: Bearer <your_access_token>
```

Response 是一个成员数组；每个成员包含 `user`（用户名）。在添加用户调用中使用 `user`。

**步骤 2 – 添加到资源组**

在常规的添加用户请求中使用第 1 步中的用户名：

```http
POST /api/organizations/{org_name}/resource-groups/{resource_group_id}/users
Content-Type: application/json
Body: { "users": [{ "user": "<username from step 1>", "role": "read" }] }
```

**示例：一封电子邮件（bash）**

```bash
ORG_NAME="my-org"
RG_ID="507f1f77bcf86cd799439011"
EMAIL="member@org.com"

# Step 1: look up member by email (domain must match org's Organization email domain or SSO allowed domains)
MEMBERS=$(curl -s -H "Authorization: Bearer $HF_TOKEN" \
  "https://huggingface.co/api/organizations/$ORG_NAME/members?email=$EMAIL&limit=1")
USERNAME=$(echo "$MEMBERS" | jq -r '(.[0] // {} | .user // "")')
if [ -z "$USERNAME" ]; then
  echo "No member found for $EMAIL"
  exit 1
fi
# Step 2: add to resource group
curl -s -X POST -H "Authorization: Bearer $HF_TOKEN" -H "Content-Type: application/json" \
  -d "{\"users\":[{\"user\":\"$USERNAME\",\"role\":\"read\"}]}" \
  "https://huggingface.co/api/organizations/$ORG_NAME/resource-groups/$RG_ID/users"
```

**示例：在循环中处理多封电子邮件（Python）**

```python
import os
import requests

BASE = "https://huggingface.co"
ORG = "my-org"
RG_ID = "507f1f77bcf86cd799439011"
ROLE = "read"
headers = {"Authorization": f"Bearer {os.environ['HF_TOKEN']}", "Content-Type": "application/json"}

emails = ["member1@org.com", "member2@org.com"]
for email in emails:
    # Step 1: resolve email → username (email domain must match org's Organization email domain or SSO allowed domains)
    r = requests.get(f"{BASE}/api/organizations/{ORG}/members", params={"email": email, "limit": 1}, headers=headers)
    r.raise_for_status()
    members = r.json()
    if not members:
        print(f"No member found for {email}")
        continue
    username = members[0]["user"]
    # Step 2: add that user to the resource group
    add_r = requests.post(
        f"{BASE}/api/organizations/{ORG}/resource-groups/{RG_ID}/users",
        headers=headers,
        json={"users": [{"user": username, "role": ROLE}]},
    )
    if add_r.status_code == 200:
        print(f"Added {username} ({email})")
    else:
        print(f"Failed {email}: {add_r.status_code} {add_r.text}")
```

如果用户已在资源组中，add 调用将返回 `403`；脚本会将其报告为失败，您可以选择跳过或忽略该情况。

**局限性：** 仅当组织设置了 **组织邮箱域名** 和/或 **SSO 允许域名**，且邮箱域名与其中之一匹配时，邮箱过滤器才会生效。否则，无法通过成员 API 按邮箱查找；你需要另一个来源来获取邮箱 → 用户名（例如你自己的目录）。

### 通过循环调用 API 进行批量添加

您可以在一次或少数几次请求中向**一个**资源组添加许多用户（例如，将用户名列表分块处理），或者通过遍历各个资源组并为每个组调用 add-users 端点，将用户添加到**多个**资源组中。

**示例：Bash – 一个请求中包含一个组和多个用户**

```bash
#!/bin/bash
# Add a list of users to a single resource group.
# Usage: ./add-users-to-rg.sh <org_name> <resource_group_id> <role>

ORG_NAME="${1:-my-org}"
RG_ID="${2:-507f1f77bcf86cd799439011}"
ROLE="${3:-read}"

USERS="member1 member2 member3 member4"
USERS_JSON=$(echo "$USERS" | tr ' ' '\n' | while read u; do
  [ -n "$u" ] && echo "{\"user\":\"$u\",\"role\":\"$ROLE\"}"
done | paste -sd ',' -)

curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST \
  -H "Authorization: Bearer $HF_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"users\":[$USERS_JSON]}" \
  "https://huggingface.co/api/organizations/$ORG_NAME/resource-groups/$RG_ID/users"
```

**示例：Bash – 循环遍历多个组**

```bash
# Get group IDs and add users to each
curl -s -H "Authorization: Bearer $HF_TOKEN" \
  "https://huggingface.co/api/organizations/my-org/resource-groups" \
  | jq -r '.[].id' \
  | while read -r RG_ID; do
      [ -z "$RG_ID" ] && continue
      echo "Adding users to resource group $RG_ID ..."
      curl -s -X POST -H "Authorization: Bearer $HF_TOKEN" -H "Content-Type: application/json" \
        -d "{\"users\":[$USERS_JSON]}" \
        "https://huggingface.co/api/organizations/my-org/resource-groups/$RG_ID/users"
    done
```

**示例：Python – 批量添加到单个或多个组**

```python
import os
import requests

BASE_URL = "https://huggingface.co"
HF_TOKEN = os.environ.get("HF_TOKEN", "")

def list_resource_groups(org_name: str):
    r = requests.get(
        f"{BASE_URL}/api/organizations/{org_name}/resource-groups",
        headers={"Authorization": f"Bearer {HF_TOKEN}"},
    )
    r.raise_for_status()
    return r.json()

def add_users_to_resource_group(org_name: str, resource_group_id: str, users_with_roles: list):
    """users_with_roles: list of {"user": "username", "role": "read"|"write"|"contributor"|"admin"}"""
    r = requests.post(
        f"{BASE_URL}/api/organizations/{org_name}/resource-groups/{resource_group_id}/users",
        headers={"Authorization": f"Bearer {HF_TOKEN}", "Content-Type": "application/json"},
        json={"users": users_with_roles},
    )
    if r.status_code != 200:
        raise RuntimeError(f"Add users failed {r.status_code}: {r.text}")
    return r.json()

# Example: same users added to every resource group
org_name = "my-org"
role = "read"
usernames = ["member1", "member2", "member3"]
users_with_roles = [{"user": u, "role": role} for u in usernames]

for rg in list_resource_groups(org_name):
    add_users_to_resource_group(org_name, rg["id"], users_with_roles)
```

对于较长的用户名列表，请将其分块（例如每次请求 50 个），并对每个分块调用一次 API，以避免请求体过大或超时。

### 重要说明

1. **仅限用户名** — 该 API 接受 Hugging Face **用户名**，而非电子邮件地址。在调用 API 之前，您需要建立从电子邮件到用户名的映射（例如，来自您的目录或组织成员列表）。
2. **用户必须属于该组织** — 请求中的每个用户都必须是该组织的现有成员。否则，请求将返回 `403`，并附带一条消息，指出部分用户不在该组织中。
3. **幂等性** — 如果用户已在资源组中，后端可能会对该请求返回 `403`。您的脚本可以捕获错误并继续执行，或者先获取该组的 `users` 列表，然后跳过已在组中的用户。
4. **速率限制** — 对于大批量请求，建议在请求之间添加短暂延迟（例如 0.5–1 秒），以避免触发速率限制。
5. **令牌范围** — 访问令牌必须具有针对该组织的足够权限（通常至少需要“对组织设置/成员管理的写入访问权限”）。请安全地创建并存储该令牌；切勿将其提交到版本控制系统中。

---

## 通过 API 配置自动加入

[自动加入](./security-resource-groups#auto-join) 会自动将组织成员以指定角色添加到资源组中。您可以通过 API 启用或禁用此功能，并可选择是否包含所有组织成员，还是仅包含 Read+ 成员。

**启用自动加入**

```http
POST /api/organizations/{org_name}/resource-groups/{resource_group_id}/settings
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "autoJoin": {
    "enabled": true,
    "role": "read",
    "scope": "read_plus"
  }
}
```

- **路径参数**
  - `org_name`：组织标识符（例如 `my-org`）。
  - `resource_group_id`：资源组的 ID（24 位十六进制字符串；从 [列出资源组端点](#list-resource-groups) 获取 ID）。
- **正文**
  - `role`：分配给自动添加的成员的权限。取值为 `"read"`、`"contributor"`、`"write"`、`"admin"` 或 `"inherit"` 之一。
  - `scope`（可选）：要自动添加的组织成员范围。使用 `"all"` 可包含所有组织成员。使用 `"read_plus"` 可排除具有 `no_access` 组织角色的成员。省略时默认为 `"all"`。

在现有资源组上启用自动加入功能后，会立即将当前组织中符合所选范围条件的成员添加进来（回填）。

**禁用自动加入**

发送带有 `"enabled": false` 的相同请求。禁用时，`role` 字段不是必需的：

```http
POST /api/organizations/{org_name}/resource-groups/{resource_group_id}/settings
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "autoJoin": {
    "enabled": false
  }
}
```

> [!NOTE]
> 禁用自动加入功能**不会**移除之前通过自动加入的成员。它仅阻止未来的组织成员被自动添加。现有成员仍保留在 Resource Group 中。

## 通过 API 设置支出限制

[支出限制](./security-resource-groups#spend-limits)用于限制归属于某个资源组的月度计算支出。使用更新端点来设置它们：

```http
PATCH /api/organizations/{org_name}/resource-groups/{resource_group_id}
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "spendLimits": {
    "total": 500000,
    "spaces": 100000,
    "jobs": 50000
  }
}
```

- **路径参数**
  - `org_name`：组织标识符（例如 `my-org`）。
  - `resource_group_id`：资源组的 ID（24 位十六进制字符串；从 [列出资源组端点](#list-resource-groups) 获取 ID）。
- **正文**
  - `spendLimits`：以**美分**为单位的月度限额。上述示例设置了 5,000 美元的总限额，其中 Spaces 为 1,000 美元，Jobs 为 500 美元。
    - `total`：该组在所有产品上的总支出上限。
    - `inferenceProviders`、`spaces`、`jobs`、`endpoints`：按产品划分的限制，叠加在总限制之上。两者中更严格的限制生效。

仅更新您发送的键，其他键保持当前值。发送 `null` 以移除限制：

```http
PATCH /api/organizations/{org_name}/resource-groups/{resource_group_id}
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "spendLimits": {
    "jobs": null
  }
}
```

**示例（curl）**

```bash
curl -s -X PATCH \
  -H "Authorization: Bearer $HF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"spendLimits": {"total": 500000}}' \
  "https://huggingface.co/api/organizations/my-org/resource-groups/507f1f77bcf86cd799439011"
```

响应为更新后的资源组，包括其 `spendLimits`。

## 通过 API 邀请外部协作者

[外部邀请](./security-resource-groups#external-invitations)允许资源组管理员将组织外部的用户邀请到资源组中。受邀者将以外部协作者的身份加入，并拥有 `no_access` 组织角色。他们会计入您组织的付费席位数量。

**启用外部邀请**

组织管理员可以通过以下方式启用该设置：

```http
POST /api/organizations/{org_name}/resource-groups/settings
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "allowExternalInvites": true
}
```

- **正文**
  - `allowExternalInvites`（可选）：资源组管理员是否可邀请组织外部的用户。
  - `minRoleToCreate`（可选）：创建[资源组](./security-resource-groups#who-can-create-resource-groups)所需的最低组织角色。

省略的字段将保持不变。使用 `GET /api/organizations/{org_name}/resource-groups/settings` 读取当前设置。

**邀请用户**

```http
POST /api/organizations/{org_name}/resource-groups/{resource_group_id}/invite
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "username": "partner-user",
  "role": "read"
}
```

- **路径参数**
  - `org_name`：组织标识符（例如 `my-org`）。
  - `resource_group_id`：资源组的 ID（24 位十六进制字符串；从 [列出资源组端点](#list-resource-groups) 获取 ID）。
- **正文**
  - `username`：尚未加入该组织的现有 Hugging Face 用户的用户名。若要添加组织成员，请改用 [将用户添加到资源组](#add-users-to-a-resource-group)。
  - `role`：受邀者在资源组中获得的角色，`"read"` 或 `"write"`。

用户接受邀请后，将收到电子邮件邀请并获得对资源组的访问权限。

**管理待处理的邀请**

- **列表：** `GET /api/organizations/{org_name}/resource-groups/{resource_group_id}/invitations`
- **重新发送：** `POST /api/organizations/{org_name}/resource-groups/{resource_group_id}/invitation/{invitation_id}/resend`
- **撤销：** `DELETE /api/organizations/{org_name}/resource-groups/{resource_group_id}/invitation/{invitation_id}`

您还可以[查看完整的 OpenAPI 规范](https://huggingface.co/spaces/huggingface/openapi#tag/resource-groups)

**常见错误：**

- `403` — 该组织未启用外部邀请。
- `409` — 该用户已是组织成员，或已收到该组织的待处理邀请。

## 通过 API 更改成员账户类型

将组织成员转换为[外部协作者](./security-sso-user-management#external-collaborators)（无需 SSO），或将其重新转换回成员。此操作需要付费计划以及在组织上拥有**Write**（或 Admin）权限。在通过 SCIM 配置成员的组织中（即托管用户组织），此功能不可用。

> [!WARNING]
> 更改用户的账户类型不会更改其角色。如需更改用户角色，请参阅 [通过 API 更改成员角色](https://huggingface.co/docs/hub/main/en/programmatic-user-access-control#change-member-role-via-api)。

```http
PUT /api/organizations/{org_name}/members/{username}/account-type
Authorization: Bearer <your_access_token>
Content-Type: application/json

{
  "accountType": "external"
}
```

- **正文**
  - `accountType`：将成员转换为外部协作者时设为 `"external"`，或将其转回成员时设为 `"member"`。

