Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

ScaiBunker Quota Reference

Profile-based resource quotas for bunker creation. Profiles bundle caps; assignments link them to users or groups; the resolver enforces the most-restrictive cap when multiple profiles apply.

For concepts, see ScaiBunker → Quotas.

Object model#

Profile#

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "id": "uuid",
  "tenant_id": "uuid | null",
  "name": "string",
  "description": "string | null",
  "max_concurrent_bunkers": 5,
  "max_persistent_bunkers": 1,
  "max_cpu_millicores": 4000,
  "max_memory_mb": 4096,
  "max_disk_mb": 8192,
  "max_gpu_count": 1,
  "per_bunker_max_cpu": null,
  "per_bunker_max_memory": null,
  "per_bunker_max_disk": null,
  "per_bunker_max_gpu": null,
  "created_at": "2026-05-09T...",
  "updated_at": "2026-05-09T..."
}
Field Meaning
tenant_id null for platform-default profiles managed by super-admin; tenant UUID for tenant-scoped profiles
max_concurrent_bunkers Cap on the number of active (non-terminal) bunkers in the bucket
max_persistent_bunkers Cap on the persistent-mode subset
max_cpu_millicores Sum of CPU across the bucket's active bunkers
max_memory_mb / max_disk_mb / max_gpu_count Same, for those resources
per_bunker_max_* Single-bunker ceiling; null = no per-bunker cap

Assignment#

json
1
2
3
4
5
6
7
8
{
  "id": "uuid",
  "profile_id": "uuid",
  "target_kind": "user | group",
  "target_id": "uuid",
  "mode": "individual | shared | per_user",
  "created_at": "2026-05-09T..."
}
target_kind mode Bucket key Meaning
user individual user:{user_id} Direct per-user quota
group shared group:{group_id} All members share one bucket
group per_user user:{user_id} Each member gets their own copy of the profile

A target (user or group ID) can have at most one assignment — UNIQUE(target_kind, target_id).

Resolution algorithm#

At bunker create:

  1. Collect all profiles applying to the caller:
    • The caller's direct user assignment, if any.
    • Every group assignment whose target_id matches a group the caller is a transitive member of (ScaiKey nested-group expansion).
  2. If nothing matches, fall back to the platform-default profile.
  3. For each profile:
    • Per-bunker checks: if any per_bunker_max_* is non-null and the request exceeds it, fail.
    • Aggregate checks: read the bucket's current usage from Redis-backed counters. If current + request exceeds any aggregate cap, fail.
  4. Most-restrictive wins because every profile's check has to pass.
  5. The platform PLATFORM_MAX_* constants are an absolute outer ceiling; no profile can grant past them.

When a check fails, the response is BUNKER_QUOTA_EXCEEDED with a message naming the cap and bucket (e.g. "Per-bunker CPU 8000m exceeds profile 'team-shared' cap of 4000m" or "Concurrent bunker limit (5) reached on group:G1 (profile 'team-shared')").

Endpoints#

GET /v1/modules/scaibunker/quota-profiles#

List visible profiles.

  • Super-admin: all profiles platform-wide.
  • Tenant caller: tenant-owned profiles plus platform defaults.
  • Anyone with scaibunker:create may read (the assignment dropdowns and the resolver fallback both need them).

Response:

json
1
2
3
4
5
6
7
8
{
  "status": "success",
  "data": {
    "items": [
      { "...QuotaProfile..." }
    ]
  }
}

POST /v1/modules/scaibunker/quota-profiles#

Create a profile.

  • Super-admin → creates a platform-default (tenant_id: null).
  • Tenant admin (scaibunker:admin:tenant) → creates a tenant-scoped profile. tenant_id is auto-set from the caller's tenant.

Request body:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "name": "team-shared",
  "description": "ML team shared bucket",
  "max_concurrent_bunkers": 10,
  "max_persistent_bunkers": 2,
  "max_cpu_millicores": 16000,
  "max_memory_mb": 32768,
  "max_disk_mb": 65536,
  "max_gpu_count": 4,
  "per_bunker_max_cpu": 4000,
  "per_bunker_max_memory": 8192,
  "per_bunker_max_disk": null,
  "per_bunker_max_gpu": 1
}

Returns 201 Created with the full profile. Fails with QUOTA_PROFILE_CONFLICT if the (tenant_id, name) pair already exists.

GET /v1/modules/scaibunker/quota-profiles/{id}#

Fetch one profile. Visibility rules same as list.

PATCH /v1/modules/scaibunker/quota-profiles/{id}#

Partial update. Only fields present in the body are touched.

  • Tenant-scoped profile → tenant admin in same tenant, or super-admin.
  • Platform-default → super-admin only.
json
1
{ "max_concurrent_bunkers": 20 }

DELETE /v1/modules/scaibunker/quota-profiles/{id}#

Delete a profile. Same auth rules as PATCH. Cascades to assignments; affected users fall back to the platform default.

GET /v1/modules/scaibunker/quota-profiles/{id}/assignments#

List assignments for one profile.

json
1
2
3
4
5
6
7
8
{
  "status": "success",
  "data": {
    "items": [
      { "...QuotaAssignment..." }
    ]
  }
}

POST /v1/modules/scaibunker/quota-profiles/{id}/assignments#

Assign a user or group to a profile.

json
1
2
3
4
5
{
  "target_kind": "user",
  "target_id": "<user-uuid>",
  "mode": "individual"
}

or

json
1
2
3
4
5
{
  "target_kind": "group",
  "target_id": "<scaikey-group-id>",
  "mode": "per_user"
}

Validation:

  • target_kind: usermode must be individual.
  • target_kind: groupmode must be shared or per_user.

Returns 409 QUOTA_ASSIGNMENT_CONFLICT if (target_kind, target_id) is already assigned to any profile.

DELETE /v1/modules/scaibunker/quota-profiles/{id}/assignments/{assignment_id}#

Remove an assignment. Idempotent — already-deleted assignments return 204 rather than 404.

Error codes#

Code HTTP Cause
QUOTA_PROFILE_NOT_FOUND 404 Profile doesn't exist or isn't visible to caller
QUOTA_ASSIGNMENT_CONFLICT 409 Target already has an assignment
BUNKER_QUOTA_EXCEEDED 400 A profile's cap would be exceeded by the request
AUTHZ_PERMISSION_DENIED 403 Caller lacks scaibunker:admin:tenant (tenant-scope) or super-admin (platform-default)

Worked example#

A tenant admin builds a quota plan for the ML team:

  1. Create a team-shared profile capping the ML group's combined usage at 16 GPU, 64 CPU, 64 GiB memory, 16 concurrent bunkers, plus a per-bunker cap of 4 GPU / 16 CPU / 16 GiB so no single user can monopolise. (POST /quota-profiles)
  2. Assign the ML group with mode=shared. (POST /quota-profiles/{id}/assignments)
  3. Create a senior-ml profile with bigger per-bunker caps (8 GPU, 32 CPU) for senior engineers who need to run large training jobs.
  4. Assign individual senior engineers with mode=individual. Their direct assignment stacks with the group shared assignment — they get the bigger per-bunker headroom and still consume from the shared aggregate bucket. Most-restrictive wins, so the group bucket is still the binding constraint on aggregate usage.

Result: the team's collective spend is bounded; juniors can't spawn giant bunkers; seniors can but still share the team budget with everyone else.

Updated 2026-05-18 15:01:29 View source (.md) rev 17