Feature access & grants¶
b'nerd uses a ceiling/grant model to control which managed applications and platform features a project can activate. The model has two tiers:
- Org ceiling — the platform sets an upper bound per feature on your organization. Org Admins can see the ceiling but cannot write it.
- Project grant — an Org Admin distributes capacity within the ceiling to individual projects. The effective access a project holds is always
ceiling ∩ grant(enforced at both grant time and app-create time — see Runtime enforcement below).
Platform (SuperAdmin)
└─ sets ceiling on OrganizationFeature
e.g. apps: allowed_kinds = [Managed::Gitlab::V1, Databases::Postgres::V1]
Org Admin
└─ grants within ceiling to a project
POST /organizations/{organization_id}/projects/{project_id}/feature-grants
{
"feature_key": "apps",
"approved_configuration": { "allowed_kinds": ["Managed::Gitlab::V1"] }
}
Project
└─ holds the effective grant
approved_configuration.allowed_kinds = ["Managed::Gitlab::V1"]
Absent ceiling means unbounded¶
If the platform has not set a ceiling for your organization on a given feature key, the org is treated as unbounded — Org Admins can grant any kind the platform supports for that feature. This is the default for all organizations, preserving behavior for existing projects and the automatic new-project grant.
Who can do what¶
| Actor | Can do |
|---|---|
| Platform admin | Set the org ceiling (ceiling_configuration). Org admins receive 403 when attempting to write this field. |
| Org Admin | Read the ceiling via GET /organizations/{id}/features/{feature_key}. Grant any kind within the ceiling to a project via POST /organizations/{organization_id}/projects/{project_id}/feature-grants. |
| Member (non-admin) | Request feature access via POST /projects/{project_id}/feature_requests. Requests are subject to the org's auto-approve policy and the ceiling. |
Org feature configuration¶
Org Admins can read and update their organization's feature settings.
Get feature configuration¶
Returns the current configuration for the feature in your organization.
Response fields
| Field | Type | Description |
|---|---|---|
feature_key | string | Feature identifier (e.g. apps, k8s, object_storage) |
enabled | boolean | Whether the feature is available for request in this org |
auto_approve | boolean | Whether member requests are auto-approved |
default_configuration | object | Configuration stamped on auto-approved grants |
ceiling_configuration | object | null | Read-only, platform-managed. The upper bound the platform has set for this org. null means unbounded. Org Admins cannot write this field. |
Update feature configuration¶
PATCH /organizations/{id}/features/{feature_key}
Authorization: Bearer <token>
Content-Type: application/json
{
"enabled": true,
"auto_approve": false,
"default_configuration": {
"allowed_kinds": ["Apps::App::V1"]
}
}
Org Admins can update enabled, auto_approve, default_configuration, and auto_approve_configuration. The ceiling_configuration field is platform-admin-only — submitting it as an Org Admin returns 403 Forbidden with error code update_ceiling. Unknown app kinds in any configuration field return 422 Unprocessable Entity with error code ungrantable_kind.
ceiling_configuration is platform-admin-only
Org Admins cannot set ceiling_configuration. The platform sets ceilings; Org Admins manage grants within them. Passing this field as an Org Admin returns 403 — it is not silently ignored.
Project feature grants¶
Org Admins can grant feature access to individual projects within their org. Grants are bounded by the org ceiling — attempting to grant kinds outside the ceiling returns 422 with error code exceeds_ceiling.
List ceiling vs grants for a project¶
GET /organizations/{organization_id}/projects/{project_id}/feature-grants
Authorization: Bearer <token>
Returns the organization's per-feature ceiling and the project's current approved grants, side by side. Useful for seeing at a glance what is available to grant versus what has already been granted.
Response shape
{
"metadata": { "..." : "..." },
"data": {
"ceiling": {
"apps": { "allowed_kinds": ["Apps::Managed::Gitlab::V1", "Databases::Postgres::V1"] }
},
"grants": {
"apps": {
"allowed_kinds": ["Apps::Managed::Gitlab::V1"],
"effective_allowed_kinds": ["Apps::Managed::Gitlab::V1"]
}
}
}
}
ceiling reflects the org-level ceiling set by the platform. grants reflects what the project currently holds. A key absent from ceiling means that feature is unbounded for this org.
Each grant entry includes effective_allowed_kinds — the server-computed intersection of the stored grant and the current ceiling. Prefer this over the raw allowed_kinds in approved_configuration when deciding which kinds a project may currently use. If the ceiling is absent (unbounded), effective_allowed_kinds is null.
Access control
| Status | Condition |
|---|---|
200 OK | Caller is an Org Admin in this organization |
403 Forbidden | Caller is a plain member (not an Org Admin) |
404 Not Found | Organization or project not found, or caller is not a member |
Create or update a grant¶
POST /organizations/{organization_id}/projects/{project_id}/feature-grants
Authorization: Bearer <token>
Content-Type: application/json
{
"feature_key": "apps",
"approved_configuration": {
"allowed_kinds": ["Apps::Managed::Gitlab::V1"]
},
"review_notes": "Approved for Q3 GitLab pilot"
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
feature_key | string | yes | Feature to grant (e.g. apps) |
approved_configuration | object | no | Grant configuration |
approved_configuration.allowed_kinds | array of strings | no | Application kind identifiers (e.g. Apps::Managed::Gitlab::V1). Omit to grant with no kind restriction (subject to the ceiling) |
review_notes | string | no | Optional note recorded on the grant for audit purposes |
Response — 201 Created
{
"metadata": { "..." : "..." },
"data": {
"id": "pfr_uuid",
"feature_key": "apps",
"status": "approved",
"approved_configuration": {
"allowed_kinds": ["Apps::Managed::Gitlab::V1"]
}
}
}
If the project already has an active grant for the feature key, the existing grant is updated in place — kinds are replaced, not merged.
Errors
| Status | Error code | Meaning |
|---|---|---|
403 Forbidden | — | Caller is not an Org Admin |
404 Not Found | — | Organization or project not found, or caller is not a member |
422 Unprocessable Entity | exceeds_ceiling | allowed_kinds contains kinds not in the org ceiling |
422 Unprocessable Entity | ungrantable_kind | One or more kind strings are not recognized by the platform |
422 Unprocessable Entity | feature_not_available | The feature is disabled for this organization (enabled: false) |
Example: grant GitLab access to one project¶
POST /organizations/org_abc123/projects/proj_xyz789/feature-grants
Authorization: Bearer <token>
Content-Type: application/json
{
"feature_key": "apps",
"approved_configuration": {
"allowed_kinds": ["Apps::Managed::Gitlab::V1"]
},
"review_notes": "GitLab pilot — approved by Org Admin"
}
The project can now activate a managed GitLab instance. Members of the project with apps permissions can proceed without filing a feature request.
Member request flow¶
Members who are not Org Admins follow the request flow:
- Member calls
POST /projects/{project_id}/feature_requestswith the desired feature key and configuration. - If
auto_approveis enabled on the org feature and the requested kinds are within the ceiling, the request is approved immediately. - Otherwise, the request enters
pendingstate and an Org Admin approves or denies it via the dashboard or the API. - On approval, the project receives a grant bounded by the ceiling. Approval cannot grant kinds that exceed the org ceiling regardless of what the member requested.
Ceiling enforcement is unconditional
Every path that creates or updates a grant — member approval, auto-approve, and direct Org Admin grants — validates allowed_kinds ⊆ org ceiling before committing. There is no bypass.
Runtime enforcement — ceiling narrows app creation immediately¶
The org ceiling is enforced at two distinct points:
- Grant time — an Org Admin cannot grant kinds outside the ceiling (
422 exceeds_ceiling). - App-create time — when a project member attempts to create a new application, the platform re-checks
effective_allowed_kinds = project grant ∩ current org ceiling. If the kind is outside the effective list, the request is rejected (422).
This means narrowing a ceiling takes effect immediately for all projects in the org, without revoking existing grants or affecting running applications. Running apps continue unaffected; only new app creates are gated.
Effective allowed kinds¶
The API exposes the server-computed effective list so clients do not need to recompute it:
GET /organizations/{organization_id}/projects/{project_id}/feature-grants— each grant entry includeseffective_allowed_kinds(array of strings, ornullmeaning unbounded).GET /projects/{project_id}/capabilities(via the project detail endpoint) — theappscapability includeseffective_allowed_kinds.
Prefer effective_allowed_kinds over the raw allowed_kinds in approved_configuration when deciding which kinds a project may use — the raw field reflects the stored grant, the effective field reflects the current ceiling intersection.
Semantics table¶
| Ceiling set? | Grant has allowed_kinds? | Effective |
|---|---|---|
| No (absent) | Yes | Grant's allowed_kinds as-is |
| No (absent) | No (legacy full grant) | Unbounded — all kinds allowed |
| Yes | Yes | ceiling ∩ grant (intersection) |
| Yes | No (legacy full grant) | Ceiling's allowed_kinds |
enabled: false on an OrganizationFeature gates feature requests and delegation only — it does not affect runtime app-kind enforcement for projects that already hold a grant.