Status: Design
Date: 2026-05-09 UTC
Owner: software-architect
Refs: design.md, ADR-0054, ADR-0055
All endpoints are on the Console Flask app (console.raxx.app) unless noted. All timestamps UTC. All requests require a valid Console session cookie. All responses are JSON.
Every endpoint below requires:
1. A valid console_session cookie (validated by validate_session()).
2. The applicable RBAC role (listed per endpoint).
3. No endpoint accepts API keys or bearer tokens — session cookie only.
403 responses follow this shape:
{
"error": "forbidden",
"required_role": "<role_name>"
}
Returns the current operator's effective roles and permissions.
RBAC gate: Any valid session (no role required; returns the caller's own data).
Query parameters: none
Response 200:
{
"admin_id": "uuid",
"groups": [
{"id": "uuid", "name": "raxx-platform-admins"}
],
"roles": [
{"name": "console-token-admin", "app": "console", "via_group": "raxx-platform-admins"},
{"name": "console-token-user", "app": "console", "via_group": "raxx-platform-admins", "inherited_from": "console-token-admin"}
],
"permissions": ["console:tokens:read", "console:tokens:rotate", "console:tokens:delete"],
"ticket_grants": [
{
"id": "grant-uuid",
"role_name": "raptor-audit-support",
"ticket_id": "FreeScout:888",
"customer_id": 42,
"expires_at_utc": null
}
],
"break_glass_active": false,
"cached_at_utc": "2026-05-09T12:00:00Z"
}
Caching: The resolved role/permission set is cached in the session record. cached_at_utc reflects when the cache was last computed. The /me endpoint always returns the current cache; it does not force a recompute. To invalidate the cache (after a grant/revoke), the grant writer calls the session invalidation service.
Grant a group membership or direct role to a user.
RBAC gate: console-invite-admin
Request body:
{
"target_user_id": "admin-uuid",
"grant_type": "group",
"group_id": "group-uuid"
}
Or for a direct role grant (break-glass pattern only):
{
"target_user_id": "admin-uuid",
"grant_type": "role",
"role_id": "role-uuid",
"justification": "Emergency incident response — outage in progress",
"expires_in_seconds": 3600
}
Validation:
- target_user_id must exist in admins.
- Caller must hold console-invite-admin.
- Self-grant check: if target_user_id == g.admin_id AND the caller does not already hold the target role/group transitively → 422 "self_escalation_prohibited".
- Break-glass role grants: justification is required (minimum 20 chars). expires_in_seconds max 14400 (4h).
- If the target group is break-glass: triggers passkey re-authentication challenge before completing.
Pre-write audit: rbac_grants_audit INSERT fires before the group/role assignment. If the audit INSERT fails, the grant is not applied (returns 500).
Response 201:
{
"grant_id": "audit-row-uuid",
"target_user_id": "admin-uuid",
"group_id": "group-uuid",
"granted_at_utc": "2026-05-09T12:00:00Z"
}
Side effects:
- Invalidates session cache for target_user_id.
- If role is in the audit role family (raptor-audit-*): writes a customer_audit_events row with action=operator.rbac.grant.
- If break-glass group grant: fires Slack alert to ops@raxx.app immediately.
Revoke a group membership, direct role grant, or ticket-scoped grant.
RBAC gate: console-invite-admin (or the target user revoking their own ticket grant)
Path parameter: grant_id — the id from rbac_grants_audit.
Response 200:
{
"grant_id": "audit-row-uuid",
"revoked_at_utc": "2026-05-09T12:30:00Z"
}
Validation: - Self-revocation is permitted only for ticket-scoped grants the caller holds. - Revoking a break-glass grant requires passkey re-authentication.
Side effects: same as POST — audit row, session invalidation, optional customer notification row.
Grant a ticket-scoped temporary role to a user, bound to a specific customer + FreeScout ticket.
RBAC gate: console-invite-admin
Request body:
{
"target_user_id": "admin-uuid",
"role_name": "raptor-audit-support",
"ticket_id": "FreeScout:888",
"customer_id": 42,
"expires_in_seconds": null
}
Validation:
- ticket_id must resolve to an OPEN ticket in FreeScout via live API check.
- customer_id must exist in Raptor's users table.
- role_name must be in the set of allowed ticket-scopeable roles: ["raptor-audit-support"] (v1 scope). Other roles may be added by operator config without code ship.
- expires_in_seconds is optional; if null the grant expires only on ticket close.
Response 201:
{
"ticket_grant_id": "ticket-grant-uuid",
"role_name": "raptor-audit-support",
"ticket_id": "FreeScout:888",
"customer_id": 42,
"expires_at_utc": null,
"granted_at_utc": "2026-05-09T12:00:00Z"
}
Auto-revoke: A background job (or FreeScout webhook — see OQ-2 in design.md) monitors ticket state. On ticket RESOLVED or CLOSED, all active rbac_ticket_grants rows for that ticket_id are revoked (soft delete: revoked_at_utc set, revoke_reason=ticket_closed). A rbac_grants_audit row is inserted (event_type: ticket_expire). Session cache for affected users is invalidated.
Check whether the current session holds a specific permission, optionally scoped to a resource.
RBAC gate: Any valid session.
Query parameters:
- permission (required): e.g., raptor:audit:read-support
- resource_id (optional): e.g., customer_id for dim-3 scoped checks
- ticket_id (optional): for ticket-scoped checks
Response 200:
{
"allowed": true,
"permission": "raptor:audit:read-support",
"resolved_via": "ticket_grant",
"ticket_grant_id": "ticket-grant-uuid"
}
Or:
{
"allowed": false,
"permission": "raptor:audit:read-support",
"reason": "no_ticket_grant"
}
Notes: - This endpoint is designed for the Raptor audit reader (SC-A8) to call as a pre-check before serving audit data. - It does NOT perform the ticket status re-validation against FreeScout; that is the caller's responsibility for dim-3 requests. This endpoint only reports whether the user has the role. - Responses are not cached; every call is a live DB lookup.
Paginated audit log of all grant and revoke events.
RBAC gate: console-audit-user
Query parameters:
- target_user_id (optional): filter by affected user
- event_type (optional): one of grant, revoke, ticket_grant, ticket_expire, break_glass_grant, break_glass_expire
- from_utc / to_utc (optional): ISO timestamps; default last 30 days
- page / per_page (optional): pagination; max per_page 100
Response 200:
{
"total": 143,
"page": 1,
"per_page": 50,
"events": [
{
"id": "uuid",
"event_type": "ticket_grant",
"target_user_id": "admin-uuid",
"target_user_email_hint": "a...@raxx.app",
"group_name": null,
"role_name": "raptor-audit-support",
"ticket_id": "FreeScout:888",
"customer_id": 42,
"justification": null,
"granted_by": "admin-uuid",
"granted_by_email_hint": "k...@raxx.app",
"expires_at_utc": null,
"created_at_utc": "2026-05-09T12:00:00Z"
}
]
}
Notes:
- email_hint fields show first character + domain only (not full email) in the response. Full email is available in the DB for authorized lookups.
- This endpoint feeds the Console Grants Audit timeline page.
List all defined roles with their permissions.
RBAC gate: console-audit-user
Response 200: Array of role objects, each with name, app, description, permissions[], inherited_from[].
List all groups with their roles and member counts.
RBAC gate: console-audit-user
| HTTP | error key |
Meaning |
|---|---|---|
| 401 | unauthenticated |
No valid session cookie |
| 403 | forbidden |
Valid session but insufficient role |
| 422 | self_escalation_prohibited |
Caller attempted to grant themselves a role they don't hold |
| 422 | cycle_detected |
Proposed role inheritance edge would create a cycle |
| 422 | ticket_not_open |
Ticket-scoped grant rejected because FreeScout ticket is not OPEN |
| 429 | break_glass_rate_limited |
Too many break-glass sessions opened in 24h |
| 503 | freescout_unavailable |
FreeScout check for ticket status failed; dim-3 fails closed |