Files
KohakuHub/docs/api/settings.md
2025-10-24 18:45:55 +08:00

725 lines
16 KiB
Markdown

---
title: Settings API
description: Manage user, organization, and repository settings
icon: settings
---
# Settings API
Manage settings for users, organizations, and repositories including profiles, avatars, and LFS configuration.
## User Settings
### Get Current User Info
Get authenticated user information in HuggingFace-compatible format.
**Endpoint:** `GET /api/whoami-v2`
**Authentication:** Required
**Response:**
```json
{
"type": "user",
"id": "123",
"name": "alice",
"fullname": "alice",
"email": "alice@example.com",
"emailVerified": true,
"canPay": false,
"isPro": false,
"orgs": [
{
"name": "myorg",
"fullname": "myorg",
"roleInOrg": "admin"
}
],
"auth": {
"type": "access_token",
"accessToken": {
"displayName": "Auto-generated token",
"role": "write"
}
},
"site": {
"name": "KohakuHub",
"api": "kohakuhub",
"version": "0.0.1"
}
}
```
**Example:**
```python
import requests
response = requests.get(
"http://localhost:28080/api/whoami-v2",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
user_info = response.json()
print(f"Logged in as: {user_info['name']}")
```
---
### Get Namespace Type
Determine if a namespace is a user or organization.
**Endpoint:** `GET /api/users/{username}/type`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `username` | string | path | Yes | Username or organization name |
| `fallback` | boolean | query | No | Enable fallback to external sources (default: `true`) |
**Authentication:** Optional
**Response:**
```json
{
"type": "user",
"_source": "local"
}
```
Or:
```json
{
"type": "org",
"_source": "local"
}
```
**Example:**
```python
response = requests.get("http://localhost:28080/api/users/myorg/type")
data = response.json()
if data["type"] == "org":
print("This is an organization")
```
---
### Get User Profile
Get public user profile information.
**Endpoint:** `GET /api/users/{username}/profile`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `username` | string | path | Yes | Username to query |
| `fallback` | boolean | query | No | Enable fallback to external sources (default: `true`) |
**Authentication:** Optional
**Response:**
```json
{
"username": "alice",
"full_name": "Alice Smith",
"bio": "ML Engineer",
"website": "https://example.com",
"social_media": {
"twitter_x": "alice_ml",
"github": "alice",
"huggingface": "alice"
},
"created_at": "2025-01-01T00:00:00Z",
"_source": "local"
}
```
**Example:**
```python
response = requests.get("http://localhost:28080/api/users/alice/profile")
profile = response.json()
```
---
### Update User Settings
Update authenticated user's settings.
**Endpoint:** `PUT /api/users/{username}/settings`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `username` | string | path | Yes | Username (must match authenticated user) |
**Request Body:**
```json
{
"email": "newemail@example.com",
"full_name": "Alice Smith",
"bio": "ML Engineer and researcher",
"website": "https://example.com",
"social_media": {
"twitter_x": "alice_ml",
"threads": "alice",
"github": "alice",
"huggingface": "alice"
}
}
```
**All fields are optional:**
| Field | Type | Description |
|-------|------|-------------|
| `email` | string | Email address (triggers re-verification) |
| `full_name` | string | Full display name |
| `bio` | string | User biography |
| `website` | string | Personal website URL |
| `social_media` | object | Social media handles |
**Authentication:** Required (can only update own settings)
**Response:**
```json
{
"success": true,
"message": "User settings updated successfully"
}
```
**Example:**
```python
response = requests.put(
"http://localhost:28080/api/users/alice/settings",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={
"bio": "ML Engineer and researcher",
"website": "https://alice.example.com"
}
)
```
---
### Manage User Avatar
#### Upload Avatar
**Endpoint:** `POST /api/users/{username}/avatar`
**Authentication:** Required (can only upload own avatar)
**Request:** Multipart form data with image file
Supported formats: JPEG, PNG, WebP, GIF
Maximum size: 10 MB
**Processing:**
- Images are resized to max 1024x1024 (maintains aspect ratio)
- Center-cropped to square
- Converted to JPEG (quality: 95)
**Response:**
```json
{
"success": true,
"message": "Avatar uploaded successfully",
"size_bytes": 245678
}
```
#### Get Avatar
**Endpoint:** `GET /api/users/{username}/avatar`
**Authentication:** Optional
**Response:** JPEG image with cache headers
**Cache-Control:** `public, max-age=86400` (24 hours)
#### Delete Avatar
**Endpoint:** `DELETE /api/users/{username}/avatar`
**Authentication:** Required (can only delete own avatar)
**Response:**
```json
{
"success": true,
"message": "Avatar deleted successfully"
}
```
**Example:**
```python
# Upload avatar
with open("avatar.png", "rb") as f:
response = requests.post(
"http://localhost:28080/api/users/alice/avatar",
headers={"Authorization": "Bearer YOUR_TOKEN"},
files={"file": f}
)
# Get avatar URL
avatar_url = "http://localhost:28080/api/users/alice/avatar"
# Delete avatar
response = requests.delete(
"http://localhost:28080/api/users/alice/avatar",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
```
---
## Organization Settings
### Get Organization Profile
Get public organization profile information.
**Endpoint:** `GET /api/organizations/{org_name}/profile`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `org_name` | string | path | Yes | Organization name |
**Authentication:** Optional
**Response:**
```json
{
"name": "myorg",
"description": "Machine Learning Research",
"bio": "We build open-source ML models",
"website": "https://myorg.example.com",
"social_media": {
"twitter_x": "myorg_ml",
"github": "myorg"
},
"member_count": 5,
"created_at": "2025-01-01T00:00:00Z"
}
```
---
### Update Organization Settings
Update organization settings (admin only).
**Endpoint:** `PUT /api/organizations/{org_name}/settings`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `org_name` | string | path | Yes | Organization name |
**Request Body:**
```json
{
"description": "Machine Learning Research",
"bio": "We build open-source ML models",
"website": "https://myorg.example.com",
"social_media": {
"twitter_x": "myorg_ml",
"threads": "myorg",
"github": "myorg",
"huggingface": "myorg"
}
}
```
**All fields are optional:**
| Field | Type | Description |
|-------|------|-------------|
| `description` | string | Organization description |
| `bio` | string | Organization biography |
| `website` | string | Organization website URL |
| `social_media` | object | Social media handles |
**Authentication:** Required (admin or super-admin role)
**Response:**
```json
{
"success": true,
"message": "Organization settings updated successfully"
}
```
**Example:**
```python
response = requests.put(
"http://localhost:28080/api/organizations/myorg/settings",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={
"description": "Machine Learning Research",
"website": "https://myorg.example.com"
}
)
```
---
### Manage Organization Avatar
#### Upload Avatar
**Endpoint:** `POST /api/organizations/{org_name}/avatar`
**Authentication:** Required (admin or super-admin)
**Request:** Multipart form data with image file
Same processing as user avatars (1024x1024, center-cropped, JPEG).
**Response:**
```json
{
"success": true,
"message": "Avatar uploaded successfully",
"size_bytes": 245678
}
```
#### Get Avatar
**Endpoint:** `GET /api/organizations/{org_name}/avatar`
**Authentication:** Optional
**Response:** JPEG image with cache headers
#### Delete Avatar
**Endpoint:** `DELETE /api/organizations/{org_name}/avatar`
**Authentication:** Required (admin or super-admin)
**Response:**
```json
{
"success": true,
"message": "Avatar deleted successfully"
}
```
**Example:**
```python
# Upload organization avatar
with open("org-logo.png", "rb") as f:
response = requests.post(
"http://localhost:28080/api/organizations/myorg/avatar",
headers={"Authorization": "Bearer YOUR_TOKEN"},
files={"file": f}
)
```
---
## Repository Settings
### Update Repository Settings
Update repository configuration including visibility and LFS settings.
**Endpoint:** `PUT /api/{repo_type}s/{namespace}/{name}/settings`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `repo_type` | string | path | Yes | Repository type: `model`, `dataset`, or `space` |
| `namespace` | string | path | Yes | Repository namespace |
| `name` | string | path | Yes | Repository name |
**Request Body:**
```json
{
"private": false,
"lfs_threshold_bytes": 10000000,
"lfs_keep_versions": 10,
"lfs_suffix_rules": [".safetensors", ".bin", ".gguf"]
}
```
**All fields are optional:**
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| `private` | boolean | Repository visibility | Checks quota before changing |
| `lfs_threshold_bytes` | integer | Size threshold for LFS (bytes) | Min: 1,000,000 (1 MB), null = server default |
| `lfs_keep_versions` | integer | Number of LFS versions to keep | Min: 2, null = server default |
| `lfs_suffix_rules` | array[string] | File extensions to always use LFS | Must start with '.', null = no custom rules |
**Note:** All size values use **decimal units** (1 MB = 1,000,000 bytes, not 1,048,576).
**Authentication:** Required (write permission)
**Response:**
```json
{
"success": true,
"message": "Repository settings updated successfully"
}
```
**Error Responses:**
| Status | Description |
|--------|-------------|
| 400 | Invalid settings or quota exceeded |
| 403 | Not authorized |
| 404 | Repository not found |
**Example:**
```python
# Update repository visibility and LFS settings
response = requests.put(
"http://localhost:28080/api/models/myorg/mymodel/settings",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={
"private": False,
"lfs_threshold_bytes": 10000000, # 10 MB
"lfs_keep_versions": 10,
"lfs_suffix_rules": [".safetensors", ".bin", ".gguf"]
}
)
# Make repository private
response = requests.put(
"http://localhost:28080/api/models/myorg/mymodel/settings",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={"private": True}
)
# Reset LFS threshold to server default
response = requests.put(
"http://localhost:28080/api/models/myorg/mymodel/settings",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={"lfs_threshold_bytes": None}
)
```
---
### Get Repository LFS Settings
Get repository LFS configuration with both configured and effective values.
**Endpoint:** `GET /api/{repo_type}s/{namespace}/{name}/settings/lfs`
**Parameters:**
| Parameter | Type | Location | Required | Description |
|-----------|------|----------|----------|-------------|
| `repo_type` | string | path | Yes | Repository type: `model`, `dataset`, or `space` |
| `namespace` | string | path | Yes | Repository namespace |
| `name` | string | path | Yes | Repository name |
**Authentication:** Required (write permission)
**Response:**
```json
{
"lfs_threshold_bytes": 10000000,
"lfs_keep_versions": 10,
"lfs_suffix_rules": [".safetensors", ".bin"],
"lfs_threshold_bytes_effective": 10000000,
"lfs_threshold_bytes_source": "repository",
"lfs_keep_versions_effective": 10,
"lfs_keep_versions_source": "repository",
"lfs_suffix_rules_effective": [
".safetensors", ".bin", ".pt", ".pth", ".ckpt",
".onnx", ".pb", ".h5", ".tflite", ".gguf", ".ggml",
".msgpack", ".zip", ".tar", ".gz", ".bz2", ".xz",
".7z", ".rar", ".npy", ".npz", ".arrow", ".parquet",
".mp4", ".avi", ".mkv", ".mov", ".wav", ".mp3",
".flac", ".tiff", ".tif"
],
"lfs_suffix_rules_source": "merged",
"server_defaults": {
"lfs_threshold_bytes": 5000000,
"lfs_keep_versions": 5,
"lfs_suffix_rules_default": [
".safetensors", ".bin", ".pt", ".pth", ".ckpt",
".onnx", ".pb", ".h5", ".tflite", ".gguf", ".ggml",
".msgpack", ".zip", ".tar", ".gz", ".bz2", ".xz",
".7z", ".rar", ".npy", ".npz", ".arrow", ".parquet",
".mp4", ".avi", ".mkv", ".mov", ".wav", ".mp3",
".flac", ".tiff", ".tif"
]
}
}
```
**Field Descriptions:**
- **Configured values** (null = using server default):
- `lfs_threshold_bytes`: Repository-specific threshold
- `lfs_keep_versions`: Repository-specific keep versions
- `lfs_suffix_rules`: Repository-specific custom suffix rules
- **Effective values** (resolved with server defaults):
- `*_effective`: Actual values being used
- `*_source`: Where the value comes from (`repository` or `server_default` or `merged`)
- **Server defaults** (for reference):
- Global default values from configuration
- 32 built-in suffix rules for ML models, archives, and media files
**Important Notes:**
1. **Suffix rule merging**: Repository suffix rules are **ADDED TO** (not replacing) server defaults
2. **Server default suffixes**: Files with these extensions ALWAYS use LFS, even if size < threshold
3. **Size units**: All sizes use decimal units (1 MB = 1,000,000 bytes)
**Example:**
```python
response = requests.get(
"http://localhost:28080/api/models/myorg/mymodel/settings/lfs",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
lfs_settings = response.json()
print(f"Threshold: {lfs_settings['lfs_threshold_bytes_effective']} bytes")
print(f"Keep versions: {lfs_settings['lfs_keep_versions_effective']}")
print(f"Custom suffixes: {lfs_settings['lfs_suffix_rules']}")
print(f"Total suffixes (with defaults): {len(lfs_settings['lfs_suffix_rules_effective'])}")
```
---
## Server Default LFS Suffix Rules
KohakuHub includes **32 server-wide default suffix rules** that always use LFS:
**ML Model Formats (12):**
`.safetensors`, `.bin`, `.pt`, `.pth`, `.ckpt`, `.onnx`, `.pb`, `.h5`, `.tflite`, `.gguf`, `.ggml`, `.msgpack`
**Compressed Archives (9):**
`.zip`, `.tar`, `.gz`, `.bz2`, `.xz`, `.7z`, `.rar`
**Data Files (4):**
`.npy`, `.npz`, `.arrow`, `.parquet`
**Media Files (5):**
`.mp4`, `.avi`, `.mkv`, `.mov`, `.wav`, `.mp3`, `.flac`
**Large Images (2):**
`.tiff`, `.tif`
These rules apply to ALL repositories automatically. Repository-specific suffix rules ADD to these defaults.
---
## Usage Examples
### Complete User Profile Update
```python
import requests
BASE_URL = "http://localhost:28080"
TOKEN = "YOUR_TOKEN"
headers = {"Authorization": f"Bearer {TOKEN}"}
# Update profile
requests.put(
f"{BASE_URL}/api/users/alice/settings",
headers=headers,
json={
"full_name": "Alice Smith",
"bio": "ML Engineer specializing in NLP",
"website": "https://alice.example.com",
"social_media": {
"twitter_x": "alice_ml",
"github": "alice",
"huggingface": "alice"
}
}
)
# Upload avatar
with open("avatar.jpg", "rb") as f:
requests.post(
f"{BASE_URL}/api/users/alice/avatar",
headers=headers,
files={"file": f}
)
```
### Repository LFS Configuration
```python
# Configure repository to always use LFS for GGUF files
requests.put(
f"{BASE_URL}/api/models/myorg/mymodel/settings",
headers=headers,
json={
"lfs_threshold_bytes": 10000000, # 10 MB
"lfs_suffix_rules": [".gguf", ".ggml"] # Added to 32 server defaults
}
)
# Check effective settings
response = requests.get(
f"{BASE_URL}/api/models/myorg/mymodel/settings/lfs",
headers=headers
)
settings = response.json()
# All .safetensors files will use LFS (server default)
# All .gguf files will use LFS (custom rule)
# Files > 10 MB will use LFS (threshold)
```
---
## Next Steps
- [Organization Management API](./organizations.md) - Create and manage organizations
- [Quota Management API](./quota.md) - Manage storage quotas
- [Repository API](../API.md#repositories) - Create and manage repositories
- [CLI Settings Commands](../CLI.md#settings) - Manage settings via CLI