User Quota System#
The quota system manages and tracks resource usage for JupyterHub users. It enables administrators to allocate, monitor, and control how much compute time users can consume.
Overview#
Key Concepts#
Balance: The amount of quota credits a user has available
Rate: The cost per minute for different resource types (CPU, GPU, NPU)
Session: A tracked period of container usage that consumes quota
Unlimited Quota: Special status exempting users from quota deductions
How It Works#
User requests to start a container
System checks if user has sufficient quota for the estimated runtime
If sufficient, a usage session begins tracking time
When container stops, actual usage is calculated and quota is deducted
Formula:
quota_consumed = rate × duration_minutes
Configuration#
Configure the quota system in your Helm values.yaml under custom.quota:
custom:
quota:
enabled: null # Enable/disable quota system (null = auto-detect based on authMode)
cpuRate: 1 # Cost per minute for CPU-only containers
minimumToStart: 10 # Minimum quota required to start any container
defaultQuota: 0 # Default quota granted to new users (0 = no initial allocation)
New User Default Quota#
The defaultQuota setting controls how much quota new users receive automatically:
Value
0(default): New users start with zero quota and must be manually granted quota by an administratorValue
> 0: New users are automatically granted this amount when they first attempt to use the system
Example configuration:
custom:
quota:
defaultQuota: 100 # Automatically grant 100 quota units to new users
How It Works#
This automatic allocation happens when a new user first tries to start a container. The system will:
Check if the user has a quota record in the database
If not found and
defaultQuota > 0, create a record with the default amountRecord this as an “initial_grant” transaction in the audit log
Unlimited Quota#
Unlimited quota status is managed via the Admin UI. To grant unlimited quota to a user:
Go to the Admin Panel (
/hub/admin/users)Click on the user’s quota value
Enter
-1,∞, orunlimitedin the input fieldClick Save
Quota Rates by Resource Type#
Each accelerator type can have a different quota rate defined in custom.accelerators:
custom:
accelerators:
phx:
quotaRate: 2 # Phoenix iGPU: 2 quota/minute
strix:
quotaRate: 2 # Strix iGPU: 2 quota/minute
strix-halo:
quotaRate: 3 # Strix Halo iGPU: 3 quota/minute
dgpu:
quotaRate: 4 # Discrete GPU: 4 quota/minute
strix-npu:
quotaRate: 1 # NPU: 1 quota/minute
Auto-Enable Behavior#
The quota system is automatically enabled/disabled based on authentication mode:
Enabled:
github,multiauthentication modesDisabled:
auto-login,dummymodes (typically for development)
Scheduled Quota Refresh (CronJob)#
You can configure automatic quota refresh rules that run on a schedule. Each rule creates a Kubernetes CronJob.
Basic Setup#
Add refresh rules to your values.yaml:
custom:
quota:
refreshRules:
daily-topup:
enabled: true
schedule: "0 0 * * *" # Every day at midnight
action: add # add or set
amount: 100 # Add 100 quota daily
maxBalance: 500 # Cap balance at 500
targets:
includeUnlimited: false # Skip unlimited users
Configuration Options#
Field |
Type |
Description |
|---|---|---|
|
bool |
Enable/disable this rule |
|
string |
Cron expression (e.g., |
|
string |
|
|
int |
Quota amount (positive to add, negative to deduct) |
|
int |
Maximum balance cap (for |
|
int |
Minimum balance floor (prevents going negative) |
|
object |
Targeting rules (see below) |
Targeting Options#
Filter which users are affected:
Target |
Type |
Description |
|---|---|---|
|
bool |
Include users with unlimited quota |
|
int |
Only users with balance below this value |
|
int |
Only users with balance above this value |
|
list |
Only these specific usernames |
|
list |
Exclude these usernames |
|
string |
Regex pattern (e.g., |
Example Rules#
custom:
quota:
refreshRules:
# Daily top-up: Add 100 to users below 400
daily-topup:
enabled: true
schedule: "0 0 * * *"
action: add
amount: 100
maxBalance: 500
targets:
includeUnlimited: false
balanceBelow: 400
# Monthly reset: Set everyone to 500
monthly-reset:
enabled: true
schedule: "0 0 1 * *" # 1st of each month
action: set
amount: 500
targets:
includeUnlimited: false
# Weekly decay: Deduct 50 from users above 100
weekly-decay:
enabled: false
schedule: "0 0 * * 0" # Every Sunday
amount: -50
minBalance: 0
targets:
balanceAbove: 100
Cron Schedule Syntax#
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *
Common examples:
"0 0 * * *"- Daily at midnight"0 8 * * 1-5"- Weekdays at 8:00 AM"0 0 1 * *"- 1st of each month at midnight"0 0 * * 0"- Every Sunday at midnight"*/30 * * * *"- Every 30 minutes
Verify CronJobs#
After helm upgrade, check if CronJobs are created:
# List quota refresh CronJobs
kubectl -n jupyterhub get cronjobs -l app.kubernetes.io/component=quota-refresh
# Check recent job runs
kubectl -n jupyterhub get jobs -l app.kubernetes.io/component=quota-refresh
# View logs from last run
kubectl -n jupyterhub logs -l app.kubernetes.io/component=quota-refresh --tail=50
Admin Operations#
Web Interface (Admin Panel)#
The Admin Panel provides a graphical interface for quota management. Access it at /hub/admin/users.

Quota Column#
When the quota system is enabled, the Users page displays a Quota column showing each user’s balance.
Inline Editing#
Click directly on a user’s quota value to edit:
Enter a new number and press Enter to save
Press Escape to cancel
Enter
-1,∞, orunlimitedto grant unlimited status

Batch Operations#
Select multiple users using checkboxes
Click “Set Quota” button (appears when users are selected)

Enter the quota value:
A number (e.g.,
500) to set exact balance-1,∞, orunlimitedto grant unlimited status
Click “Apply” to update all selected users

REST API Endpoints#
All admin endpoints require authentication. Admin-specific operations require admin privileges.
List All User Quotas#
GET /admin/api/quota/
Response:
{
"users": [
{"username": "user1", "balance": 500, "unlimited": false, "updated_at": "2026-01-15T10:00:00"},
{"username": "user2", "balance": 1000, "unlimited": true, "updated_at": "2026-01-15T09:30:00"}
]
}
Get Single User Quota#
GET /admin/api/quota/<username>
Response:
{
"username": "user1",
"balance": 500,
"unlimited": false,
"recent_transactions": [
{
"id": 123,
"username": "user1",
"amount": -10,
"transaction_type": "usage",
"resource_type": "cpu",
"description": "Session 456: 10 minutes",
"balance_before": 510,
"balance_after": 500,
"created_at": "2026-01-15T10:00:00",
"created_by": null
}
]
}
Modify User Quota#
POST /admin/api/quota/<username>
Content-Type: application/json
{
"action": "set",
"amount": 100,
"unlimited": true,
"description": "Monthly allocation"
}
action:"set","add","deduct", or"set_unlimited"amount: Required for set/add/deductunlimited: Required for set_unlimited
Actions:
set: Set balance to exact amountadd: Add amount to current balancededuct: Subtract amount from balanceset_unlimited: Mark/unmark user as having unlimited quota
Response:
{
"username": "user1",
"balance": 100,
"action": "set",
"amount": 100
}
Batch Set Quota#
POST /admin/api/quota/batch
Content-Type: application/json
{
"users": [
{"username": "user1", "amount": 100},
{"username": "user2", "amount": 200}
]
}
Response:
{
"success": 2,
"failed": 0,
"details": [
{"username": "user1", "status": "success", "balance": 100},
{"username": "user2", "status": "success", "balance": 200}
]
}
Batch Refresh Quota#
Flexible batch operation with targeting rules:
POST /admin/api/quota/refresh
Content-Type: application/json
{
"rule_name": "weekly_reset",
"action": "add",
"amount": 100,
"max_balance": 1000,
"min_balance": 0,
"targets": {
"includeUnlimited": false,
"balanceBelow": 500,
"balanceAbove": 0,
"includeUsers": ["user1"],
"excludeUsers": ["admin"],
"usernamePattern": "^student_.*"
}
}
Request fields:
action:"add"or"set"max_balance: Optional cap for balancemin_balance: Optional floor for balance
Targeting Rules (AND logic):
includeUnlimited: Whether to include users marked as unlimitedbalanceBelow: Only affect users with balance below this valuebalanceAbove: Only affect users with balance above this valueincludeUsers: Whitelist of specific usernamesexcludeUsers: Blacklist of usernames to skipusernamePattern: Regex pattern to match usernames
Response:
{
"users_updated": 25,
"total_change": 2500,
"skipped": 5,
"action": "add",
"rule_name": "weekly_reset"
}
CLI Commands (manage_users.py)#
The manage_users.py script provides command-line quota management via kubectl.
Set Quota#
Set quota to a specific amount:
# Set quota for specific users
python scripts/manage_users.py set-quota user1 user2 --amount 1000
# Set quota from file (requires username column, optional quota column)
python scripts/manage_users.py set-quota -f users.csv --amount 500
# File with per-user amounts
python scripts/manage_users.py set-quota -f users_with_quota.csv
File format (users_with_quota.csv):
username,quota
student01,500
student02,1000
teacher01,2000
Add Quota#
Add quota to existing balance:
# Add to specific users
python scripts/manage_users.py add-quota user1 user2 --amount 100
# Add to users from file
python scripts/manage_users.py add-quota -f users.csv --amount 50
List Quota Balances#
Display all user quota balances:
python scripts/manage_users.py list-quota
Output:
📋 Quota Balances (25 users):
Username Balance Last Updated
-----------------------------------------------------------------
student01 450 2026-01-15 10:30:00
student02 800 2026-01-15 09:15:00
teacher01 1500 2026-01-14 16:45:00
User Experience#
Viewing Personal Quota#
Users can check their quota via the API:
GET /api/quota/me
Response:
{
"username": "student01",
"balance": 450,
"unlimited": false,
"rates": {"cpu": 1, "phx": 2, "strix": 2},
"enabled": true
}
To get accelerator options, use the separate /api/accelerators endpoint.
Quota Rates API#
Get quota rates and configuration (available to all authenticated users):
GET /api/quota/rates
Response:
{
"enabled": true,
"rates": {"cpu": 1, "phx": 2, "strix": 2},
"minimum_to_start": 10
}
enabled: Whether the quota system is activerates: Quota consumption rate per minute for each resource typeminimum_to_start: Minimum quota required to start any container
Accelerators API#
Get available accelerator options (always available, independent of quota system):
GET /api/accelerators
Response:
{
"accelerators": {
"phx": {
"displayName": "AMD Radeon™ 780M (Phoenix Point iGPU)",
"description": "RDNA 3.0 (gfx1103) | Compute Units 12 | 4GB LPDDR5X",
"nodeSelector": {"accelerator": "phx"},
"quotaRate": 2
}
}
}
accelerators: Available accelerator options with display name, description, node selector, and quota rate
Insufficient Quota#
When a user attempts to start a container without sufficient quota:
System calculates estimated cost:
rate × requested_runtimeCompares with current balance and minimum requirement
If insufficient, returns HTTP 403 error with message:
Cannot start container: Insufficient quota. Current balance: 5,
estimated cost: 120 (2 quota/min × 60 min).
Please contact administrator to add quota.
Quota Deduction#
Quota is deducted when a container stops:
System records actual duration (minimum 1 minute)
Calculates actual cost:
rate × actual_duration_minutesDeducts from user balance
Records transaction in audit log
Technical Details#
Stale Session Cleanup#
Sessions stuck for more than 8 hours are automatically cleaned up on hub startup:
Marks session as
cleaned_upRecords duration but does NOT deduct quota (avoids charging for crashed sessions)
Logs cleanup for auditing
Unlimited Quota Logic#
A user has unlimited quota if marked unlimited: true in the database.
Troubleshooting#
User Cannot Start Container#
Symptom: “Insufficient quota” error
Solutions:
Check user’s current balance:
python scripts/manage_users.py list-quota | grep username
Add quota to user:
python scripts/manage_users.py add-quota username --amount 500
Or grant unlimited quota via Admin UI (click quota value and enter
∞)
Quota Not Being Deducted#
Symptom: User’s balance doesn’t decrease after container use
Possible causes:
User has unlimited quota (set via Admin UI)
Quota system is disabled (
custom.quota.enabled: false)Session ended abnormally (cleaned up as stale)
Check:
# View user's transaction history
curl "$JUPYTERHUB_URL/admin/api/quota/username" \
-H "Authorization: token $JUPYTERHUB_TOKEN"
Session Stuck as Active#
Symptom: Old sessions show as active even though containers stopped
Solution: Sessions are automatically cleaned on hub restart, or manually:
# Restart hub to trigger cleanup
kubectl -n jupyterhub rollout restart deployment/hub
Database Issues#
Location: /srv/jupyterhub/quota.sqlite (inside hub pod)
Access for debugging:
kubectl -n jupyterhub exec deployment/hub -- sqlite3 /srv/jupyterhub/quota.sqlite \
"SELECT * FROM user_quota LIMIT 10;"
See Also#
User Management Guide - Batch user operations
Authentication Guide - User authentication setup