--- title: Quota Management API description: Manage storage quotas for users, organizations, and repositories icon: database --- # Quota Management API Manage storage quotas at the namespace (user/organization) and repository levels. Track usage, set limits, and recalculate storage. ## Overview KohakuHub supports three-level quota management: 1. **Namespace quotas**: Overall storage limits for users and organizations - Separate quotas for public and private repositories - Tracked per namespace (user or organization) 2. **Repository quotas**: Per-repository storage limits - Can override namespace quotas - Helps prevent single repo from consuming all space 3. **Quota inheritance**: Repositories inherit namespace quota by default **All size values use decimal units** (1 MB = 1,000,000 bytes, not 1,048,576 bytes). --- ## Namespace Quota Endpoints ### Get Namespace Quota Get storage quota information for a user or organization. **Endpoint:** `GET /api/quota/{namespace}` **Parameters:** | Parameter | Type | Location | Required | Description | |-----------|------|----------|----------|-------------| | `namespace` | string | path | Yes | Username or organization name | **Authentication:** Required **Response:** ```json { "namespace": "alice", "is_organization": false, "quota_bytes": 107374182400, "used_bytes": 53687091200, "available_bytes": 53687091200, "percentage_used": 50.0 } ``` **Field Descriptions:** | Field | Type | Description | |-------|------|-------------| | `namespace` | string | Username or organization name | | `is_organization` | boolean | Whether namespace is an organization | | `quota_bytes` | integer | Total quota in bytes (null = unlimited) | | `used_bytes` | integer | Storage currently used | | `available_bytes` | integer | Remaining storage (null if unlimited) | | `percentage_used` | float | Percentage of quota used (null if unlimited) | **Example:** ```python import requests response = requests.get( "http://localhost:28080/api/quota/alice", headers={"Authorization": "Bearer YOUR_TOKEN"} ) quota = response.json() print(f"Used: {quota['used_bytes'] / 1_000_000_000:.2f} GB") print(f"Available: {quota['available_bytes'] / 1_000_000_000:.2f} GB") print(f"Usage: {quota['percentage_used']:.1f}%") ``` --- ### Set Namespace Quota Set storage quota for a user or organization. **Endpoint:** `PUT /api/quota/{namespace}` **Parameters:** | Parameter | Type | Location | Required | Description | |-----------|------|----------|----------|-------------| | `namespace` | string | path | Yes | Username or organization name | **Request Body:** ```json { "quota_bytes": 107374182400 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `quota_bytes` | integer | Yes | Quota in bytes (null = unlimited) | **Authorization:** - Users: Can only set their own quota - Organizations: Admin or super-admin members only **Authentication:** Required **Response:** ```json { "namespace": "alice", "is_organization": false, "quota_bytes": 107374182400, "used_bytes": 53687091200, "available_bytes": 53687091200, "percentage_used": 50.0 } ``` **Example:** ```python # Set quota to 100 GB (using decimal: 100 * 1000^3) response = requests.put( "http://localhost:28080/api/quota/alice", headers={"Authorization": "Bearer YOUR_TOKEN"}, json={"quota_bytes": 100_000_000_000} ) # Set unlimited quota response = requests.put( "http://localhost:28080/api/quota/alice", headers={"Authorization": "Bearer YOUR_TOKEN"}, json={"quota_bytes": None} ) ``` --- ### Recalculate Namespace Storage Recalculate total storage usage for a namespace (useful if tracking gets out of sync). **Endpoint:** `POST /api/quota/{namespace}/recalculate` **Parameters:** | Parameter | Type | Location | Required | Description | |-----------|------|----------|----------|-------------| | `namespace` | string | path | Yes | Username or organization name | **Authorization:** - Users: Can only recalculate their own storage - Organizations: Admin or super-admin members only **Authentication:** Required **Response:** ```json { "namespace": "alice", "is_organization": false, "quota_bytes": 107374182400, "used_bytes": 53687091200, "available_bytes": 53687091200, "percentage_used": 50.0 } ``` **Example:** ```python response = requests.post( "http://localhost:28080/api/quota/alice/recalculate", headers={"Authorization": "Bearer YOUR_TOKEN"} ) quota = response.json() print(f"Recalculated storage: {quota['used_bytes']} bytes") ``` --- ### Get Public Quota Info Get public storage quota information with permission-based visibility. **Endpoint:** `GET /api/quota/{namespace}/public` **Parameters:** | Parameter | Type | Location | Required | Description | |-----------|------|----------|----------|-------------| | `namespace` | string | path | Yes | Username or organization name | **Authentication:** Optional **Response (with permission):** ```json { "namespace": "alice", "is_organization": false, "public_quota_bytes": 53687091200, "public_used_bytes": 10737418240, "public_available_bytes": 42949672960, "public_percentage_used": 20.0, "private_quota_bytes": 53687091200, "private_used_bytes": 21474836480, "private_available_bytes": 32212254720, "private_percentage_used": 40.0, "total_used_bytes": 32212254720, "can_see_private": true } ``` **Response (without permission):** ```json { "namespace": "alice", "is_organization": false, "public_quota_bytes": 53687091200, "public_used_bytes": 10737418240, "public_available_bytes": 42949672960, "public_percentage_used": 20.0, "total_used_bytes": 10737418240, "can_see_private": false } ``` **Permission Rules:** - **Public storage**: Always visible to everyone - **Private storage**: Visible only if: - User viewing their own profile - Organization members viewing org profile **Use Case:** Profile pages displaying storage usage **Example:** ```python # Anyone can view public quota response = requests.get( "http://localhost:28080/api/quota/alice/public" ) quota = response.json() if quota["can_see_private"]: print(f"Total usage: {quota['total_used_bytes']} bytes") print(f" Public: {quota['public_used_bytes']} bytes") print(f" Private: {quota['private_used_bytes']} bytes") else: print(f"Public usage: {quota['public_used_bytes']} bytes") ``` --- ### List Repository Storage Get detailed storage breakdown for all repositories in a namespace. **Endpoint:** `GET /api/quota/{namespace}/repos` **Parameters:** | Parameter | Type | Location | Required | Description | |-----------|------|----------|----------|-------------| | `namespace` | string | path | Yes | Username or organization name | **Authorization:** - Users: Can only view their own repositories - Organizations: Members only **Authentication:** Required **Response:** ```json { "namespace": "alice", "is_organization": false, "total_repos": 3, "repositories": [ { "repo_id": "alice/large-model", "repo_type": "model", "name": "large-model", "private": false, "quota_bytes": null, "used_bytes": 50000000000, "percentage_used": null, "is_inheriting": true, "created_at": "2025-01-01T00:00:00Z" }, { "repo_id": "alice/small-dataset", "repo_type": "dataset", "name": "small-dataset", "private": true, "quota_bytes": 5000000000, "used_bytes": 2500000000, "percentage_used": 50.0, "is_inheriting": false, "created_at": "2025-01-10T00:00:00Z" } ] } ``` **Notes:** - Repositories are sorted by `used_bytes` descending (largest first) - Includes both public and private repositories - Shows which repos have custom quotas vs. inheriting **Example:** ```python response = requests.get( "http://localhost:28080/api/quota/alice/repos", headers={"Authorization": "Bearer YOUR_TOKEN"} ) data = response.json() print(f"Total repositories: {data['total_repos']}") print("\nLargest repositories:") for repo in data["repositories"][:5]: size_gb = repo["used_bytes"] / 1_000_000_000 status = "inheriting" if repo["is_inheriting"] else "custom quota" print(f" {repo['repo_id']}: {size_gb:.2f} GB ({status})") ``` --- ## Repository Quota Endpoints ### Get Repository Quota Get storage quota information for a specific repository. **Endpoint:** `GET /api/quota/repo/{repo_type}/{namespace}/{name}` **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:** Optional (required for private repositories) **Response:** ```json { "repo_id": "alice/mymodel", "repo_type": "model", "namespace": "alice", "quota_bytes": 10000000000, "used_bytes": 5000000000, "available_bytes": 5000000000, "percentage_used": 50.0, "effective_quota_bytes": 10000000000, "namespace_quota_bytes": 107374182400, "namespace_used_bytes": 50000000000, "namespace_available_bytes": 57374182400, "is_inheriting": false } ``` **Field Descriptions:** | Field | Type | Description | |-------|------|-------------| | `quota_bytes` | integer | Repository-specific quota (null if inheriting) | | `used_bytes` | integer | Storage used by this repository | | `available_bytes` | integer | Remaining storage for this repo | | `percentage_used` | float | Percentage of repo quota used | | `effective_quota_bytes` | integer | Actual enforced quota (repo or namespace) | | `namespace_quota_bytes` | integer | Namespace-level quota | | `namespace_used_bytes` | integer | Total namespace usage | | `namespace_available_bytes` | integer | Remaining namespace quota | | `is_inheriting` | boolean | Whether using namespace quota | **Example:** ```python response = requests.get( "http://localhost:28080/api/quota/repo/model/alice/mymodel", headers={"Authorization": "Bearer YOUR_TOKEN"} ) quota = response.json() print(f"Repository quota: {quota['quota_bytes']}") print(f"Used: {quota['used_bytes']} bytes ({quota['percentage_used']:.1f}%)") print(f"Inheriting from namespace: {quota['is_inheriting']}") ``` --- ### Set Repository Quota Set a custom storage quota for a repository. **Endpoint:** `PUT /api/quota/repo/{repo_type}/{namespace}/{name}` **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 { "quota_bytes": 10000000000 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `quota_bytes` | integer | Yes | Repository quota in bytes (null = inherit from namespace) | **Constraints:** - Repository quota cannot exceed namespace available quota - Must have write permission to repository **Authentication:** Required (write permission) **Response:** Same format as GET repository quota endpoint. **Example:** ```python # Set custom quota of 10 GB response = requests.put( "http://localhost:28080/api/quota/repo/model/alice/mymodel", headers={"Authorization": "Bearer YOUR_TOKEN"}, json={"quota_bytes": 10_000_000_000} ) # Reset to inherit from namespace response = requests.put( "http://localhost:28080/api/quota/repo/model/alice/mymodel", headers={"Authorization": "Bearer YOUR_TOKEN"}, json={"quota_bytes": None} ) ``` --- ### Recalculate Repository Storage Recalculate storage usage for a specific repository. **Endpoint:** `POST /api/quota/repo/{repo_type}/{namespace}/{name}/recalculate` **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:** Same format as GET repository quota endpoint. **Example:** ```python response = requests.post( "http://localhost:28080/api/quota/repo/model/alice/mymodel/recalculate", headers={"Authorization": "Bearer YOUR_TOKEN"} ) quota = response.json() print(f"Recalculated: {quota['used_bytes']} bytes") ``` --- ## Usage Examples ### Comprehensive Quota Management ```python import requests BASE_URL = "http://localhost:28080" TOKEN = "YOUR_TOKEN" headers = {"Authorization": f"Bearer {TOKEN}"} class QuotaManager: def __init__(self, base_url: str, token: str): self.base_url = base_url self.headers = {"Authorization": f"Bearer {token}"} # Namespace methods def get_namespace_quota(self, namespace: str): response = requests.get( f"{self.base_url}/api/quota/{namespace}", headers=self.headers ) response.raise_for_status() return response.json() def set_namespace_quota(self, namespace: str, quota_bytes: int = None): response = requests.put( f"{self.base_url}/api/quota/{namespace}", headers=self.headers, json={"quota_bytes": quota_bytes} ) response.raise_for_status() return response.json() def recalculate_namespace(self, namespace: str): response = requests.post( f"{self.base_url}/api/quota/{namespace}/recalculate", headers=self.headers ) response.raise_for_status() return response.json() def list_repo_storage(self, namespace: str): response = requests.get( f"{self.base_url}/api/quota/{namespace}/repos", headers=self.headers ) response.raise_for_status() return response.json() # Repository methods def get_repo_quota(self, repo_type: str, namespace: str, name: str): response = requests.get( f"{self.base_url}/api/quota/repo/{repo_type}/{namespace}/{name}", headers=self.headers ) response.raise_for_status() return response.json() def set_repo_quota(self, repo_type: str, namespace: str, name: str, quota_bytes: int = None): response = requests.put( f"{self.base_url}/api/quota/repo/{repo_type}/{namespace}/{name}", headers=self.headers, json={"quota_bytes": quota_bytes} ) response.raise_for_status() return response.json() def recalculate_repo(self, repo_type: str, namespace: str, name: str): response = requests.post( f"{self.base_url}/api/quota/repo/{repo_type}/{namespace}/{name}/recalculate", headers=self.headers ) response.raise_for_status() return response.json() # Helper methods def format_bytes(self, bytes_val: int) -> str: """Format bytes in human-readable format.""" if bytes_val is None: return "Unlimited" for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if bytes_val < 1000.0: return f"{bytes_val:.2f} {unit}" bytes_val /= 1000.0 return f"{bytes_val:.2f} PB" def print_quota_summary(self, namespace: str): """Print comprehensive quota summary.""" # Get namespace quota quota = self.get_namespace_quota(namespace) print(f"\n=== Quota Summary: {namespace} ===") print(f"Type: {'Organization' if quota['is_organization'] else 'User'}") print(f"Quota: {self.format_bytes(quota['quota_bytes'])}") print(f"Used: {self.format_bytes(quota['used_bytes'])}") print(f"Available: {self.format_bytes(quota['available_bytes'])}") if quota['percentage_used']: print(f"Usage: {quota['percentage_used']:.1f}%") # Get repository breakdown repos_data = self.list_repo_storage(namespace) print(f"\n=== Repositories ({repos_data['total_repos']}) ===") for repo in repos_data["repositories"][:10]: # Top 10 size = self.format_bytes(repo["used_bytes"]) status = "inheriting" if repo["is_inheriting"] else "custom" visibility = "private" if repo["private"] else "public" print(f" {repo['repo_id']}: {size} ({visibility}, {status})") # Usage mgr = QuotaManager(BASE_URL, TOKEN) # Set namespace quota to 100 GB mgr.set_namespace_quota("alice", 100_000_000_000) # Print summary mgr.print_quota_summary("alice") # Set custom quota for large model mgr.set_repo_quota("model", "alice", "large-model", 50_000_000_000) # Recalculate if needed mgr.recalculate_namespace("alice") ``` ### Monitor and Alert on Quota Usage ```python def check_quota_alerts(namespace: str, token: str, threshold: float = 80.0): """Check if quota usage exceeds threshold and alert.""" headers = {"Authorization": f"Bearer {token}"} # Check namespace quota response = requests.get( f"{BASE_URL}/api/quota/{namespace}", headers=headers ) quota = response.json() alerts = [] # Check overall usage if quota["percentage_used"] and quota["percentage_used"] > threshold: alerts.append({ "type": "namespace", "name": namespace, "usage": quota["percentage_used"], "message": f"Namespace '{namespace}' is at {quota['percentage_used']:.1f}% capacity" }) # Check individual repositories response = requests.get( f"{BASE_URL}/api/quota/{namespace}/repos", headers=headers ) repos = response.json()["repositories"] for repo in repos: if repo["percentage_used"] and repo["percentage_used"] > threshold: alerts.append({ "type": "repository", "name": repo["repo_id"], "usage": repo["percentage_used"], "message": f"Repository '{repo['repo_id']}' is at {repo['percentage_used']:.1f}% capacity" }) return alerts # Usage alerts = check_quota_alerts("alice", TOKEN, threshold=80.0) if alerts: print("⚠️ Quota Alerts:") for alert in alerts: print(f" {alert['message']}") else: print("✓ All quotas are healthy") ``` ### Bulk Repository Quota Management ```python def optimize_repository_quotas(namespace: str, token: str): """Automatically set repository quotas based on current usage.""" headers = {"Authorization": f"Bearer {token}"} # Get all repositories response = requests.get( f"{BASE_URL}/api/quota/{namespace}/repos", headers=headers ) repos = response.json()["repositories"] for repo in repos: # Only set quota for repos without custom quota if repo["is_inheriting"] and repo["used_bytes"] > 10_000_000_000: # > 10 GB # Set quota to 150% of current usage (with some headroom) suggested_quota = int(repo["used_bytes"] * 1.5) print(f"Setting quota for {repo['repo_id']}: {suggested_quota / 1_000_000_000:.2f} GB") requests.put( f"{BASE_URL}/api/quota/repo/{repo['repo_type']}/{namespace}/{repo['name']}", headers=headers, json={"quota_bytes": suggested_quota} ) # Usage optimize_repository_quotas("alice", TOKEN) ``` --- ## JavaScript/TypeScript Example ```javascript class QuotaAPI { constructor(baseURL, token) { this.baseURL = baseURL; this.headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }; } async getNamespaceQuota(namespace) { const response = await fetch( `${this.baseURL}/api/quota/${namespace}`, { headers: this.headers } ); return await response.json(); } async setNamespaceQuota(namespace, quotaBytes) { const response = await fetch( `${this.baseURL}/api/quota/${namespace}`, { method: 'PUT', headers: this.headers, body: JSON.stringify({ quota_bytes: quotaBytes }) } ); return await response.json(); } async listRepoStorage(namespace) { const response = await fetch( `${this.baseURL}/api/quota/${namespace}/repos`, { headers: this.headers } ); return await response.json(); } async getRepoQuota(repoType, namespace, name) { const response = await fetch( `${this.baseURL}/api/quota/repo/${repoType}/${namespace}/${name}`, { headers: this.headers } ); return await response.json(); } formatBytes(bytes) { if (!bytes) return 'Unlimited'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = bytes; let unitIndex = 0; while (size >= 1000 && unitIndex < units.length - 1) { size /= 1000; unitIndex++; } return `${size.toFixed(2)} ${units[unitIndex]}`; } } // Usage const quotaAPI = new QuotaAPI('http://localhost:28080', 'YOUR_TOKEN'); const quota = await quotaAPI.getNamespaceQuota('alice'); console.log(`Used: ${quotaAPI.formatBytes(quota.used_bytes)}`); console.log(`Available: ${quotaAPI.formatBytes(quota.available_bytes)}`); const repos = await quotaAPI.listRepoStorage('alice'); console.log(`Total repositories: ${repos.total_repos}`); ``` --- ## CLI Usage See [CLI Documentation](../CLI.md#quota) for command-line interface: ```bash # Get namespace quota kohub-cli quota get alice # Set namespace quota (100 GB) kohub-cli quota set alice --quota 100000000000 # List repository storage kohub-cli quota repos alice # Get repository quota kohub-cli quota repo get model alice/mymodel # Set repository quota (10 GB) kohub-cli quota repo set model alice/mymodel --quota 10000000000 # Recalculate storage kohub-cli quota recalculate alice ``` --- ## Next Steps - [Repository Settings API](./settings.md) - Configure repository visibility - [Statistics API](./stats.md) - Track repository downloads and activity - [Organization Management API](./organizations.md) - Manage organizations