REST API¶
All endpoints live under /api/v1. Every request must be
authenticated and carry the X-Tenant-ID header
(multi-tenancy). GET /healthz is the only
unauthenticated endpoint.
Errors are JSON: {"error": "..."} with conventional status codes —
400 validation, 401 authentication, 403 missing meta permission,
404 unknown entity/tenant, 409 duplicate.
Administrative endpoints require the listed
meta operation at FULL scope in the active
tenant.
Self-service queries¶
GET /me/operations¶
Effective operations of the caller after combining role permissions and user overrides. Restricted IDs are not included.
[
{"operation": "product:read", "scope": "FULL"},
{"operation": "invoice:read", "scope": "RESTRICTED"}
]
GET /me/record-ids?operations=op1,op2¶
Accessible record IDs for the requested operations. Only operations whose
effective scope is RESTRICTED appear in the response.
{"product:read": ["1", "2", "3"], "invoice:read": ["10", "20"]}
GET /me/meta-operations¶
Effective operations filtered to the meta authorization model.
Resources¶
| Endpoint | Meta op | Body | Response |
|---|---|---|---|
GET /resources |
resource:read |
— | [{"name": "product"}] |
POST /resources |
resource:write |
{"name": "product"} |
201 |
DELETE /resources/{name} |
resource:write |
— | 204 (operations of the resource are deleted too) |
Operations¶
| Endpoint | Meta op | Body | Response |
|---|---|---|---|
GET /operations |
operation:read |
— | [{"name": "product:read", "resource": "product"}] |
POST /operations |
operation:write |
{"name": "product:read"} |
201 (parent resource auto-created) |
DELETE /operations/{name} |
operation:write |
— | 204 |
Users¶
| Endpoint | Meta op | Description |
|---|---|---|
GET /users |
user:read |
All lazily provisioned users. |
GET /users/{sub}/operations |
user:read |
Effective operations of a user. |
GET /users/{sub}/meta-operations |
user:read |
Effective meta operations of a user. |
GET /users/{sub}/roles |
role:read |
Role names assigned to a user. |
Roles¶
| Endpoint | Meta op | Body |
|---|---|---|
GET /roles |
role:read |
— |
GET /roles/{name} |
role:read |
— (returns the role with its permissions) |
POST /roles |
role:write |
{"name": "manager"} |
PUT /roles/{name} |
role:write |
{"name": "new-name"} (rename) |
DELETE /roles/{name} |
role:write |
— (cascades permissions, IDs, assignments) |
POST /roles/{name}/assignments/{sub} |
role:assign |
— (idempotent; provisions the user) |
DELETE /roles/{name}/assignments/{sub} |
role:assign |
— |
Permission assignments¶
Operations must already be registered; scopes are upserted.
| Endpoint | Meta op | Body |
|---|---|---|
PUT /roles/{name}/permissions/{op} |
operation:assign |
{"scope": "FULL"} |
DELETE /roles/{name}/permissions/{op} |
operation:assign |
— (drops the permission's restricted IDs too) |
PUT /users/{sub}/permissions/{op} |
operation:assign |
{"scope": "RESTRICTED"} |
DELETE /users/{sub}/permissions/{op} |
operation:assign |
— |
Operation names contain : and are used directly in paths:
PUT /api/v1/roles/manager/permissions/product:read.
Restricted IDs¶
| Endpoint | Meta op | Body |
|---|---|---|
POST /roles/{name}/permissions/{op}/ids |
operation:assign |
{"add": ["10"], "remove": ["15"]} |
POST /users/{sub}/permissions/{op}/ids |
operation:assign |
{"add": ["10"], "remove": ["15"]} |
The permission must exist for the role/user, otherwise 404. Adding an
existing ID is a no-op.
Bootstrap refresh¶
POST /admin/sync¶
Re-reads every glob in bootstrap.files and applies the result (see
Bootstrap YAML). Requires resource:write,
operation:write, role:write, role:assign and operation:assign.
{"synced-files": 3}