# API Command & Query Reference

Auto-generated from the live CQRS registry at `https://api.laksonoteknologi.com/v2` on 2026-08-15 08:46 - reflects whatever's actually registered in code right now, not hand-maintained. Regenerate after adding/changing a command or query; don't hand-edit this file, edits will be overwritten.

## Commands (5)

### admin

#### `POST /v2/admin/permissions/roles/:subRoleCode/grant` - grant-role-permission

Grants a permission (obj+act, optionally scoped to a domain - defaults to the global domain if omitted) to every user holding the given sub-role, identified by subRoleCode in the path. This is a role-level grant, not per-user - it affects everyone currently or later assigned that sub-role.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:assign`  
**Middlewares:** auth:admin  
**Requires `command` field:** true

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `:subRoleCode` | path | string | true | required |
| `data.obj` | body | string | true | required |
| `data.act` | body | string | true | required |
| `data.domain` | body | string | false | omitempty |

```json
{
  "command": "grant-role-permission",
  "data": {
    "act": "example_string",
    "domain": "example_string",
    "obj": "example_string"
  }
}
```

---

#### `DELETE /v2/admin/permissions/roles/:subRoleCode/revoke` - revoke-role-permission

Revokes a previously-granted permission (obj+act, optionally scoped to a domain) from the sub-role identified by subRoleCode in the path - the inverse of grant-role-permission. Affects every user holding that sub-role.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:revoke`  
**Middlewares:** auth:admin  
**Requires `command` field:** true

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `:subRoleCode` | path | string | true | required |
| `data.obj` | body | string | true | required |
| `data.act` | body | string | true | required |
| `data.domain` | body | string | false | omitempty |

```json
{
  "command": "revoke-role-permission",
  "data": {
    "act": "example_string",
    "domain": "example_string",
    "obj": "example_string"
  }
}
```

---

#### `POST /v2/admin/permissions/users/:userId/grant` - grant-user-permission

Grants a permission (obj+act, optionally scoped to a domain) directly to a single user by userId, on top of whatever their sub-role already grants - a per-user override/exception rather than a role-wide change. Useful for one-off access without creating a new sub-role.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:assign`  
**Middlewares:** auth:admin  
**Requires `command` field:** true

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `:userId` | path | string | true | required |
| `data.obj` | body | string | true | required |
| `data.act` | body | string | true | required |
| `data.domain` | body | string | false | omitempty |

```json
{
  "command": "grant-user-permission",
  "data": {
    "act": "example_string",
    "domain": "example_string",
    "obj": "example_string"
  }
}
```

---

#### `DELETE /v2/admin/permissions/users/:userId/revoke` - revoke-user-permission

Revokes a previously-granted per-user permission override (obj+act, optionally scoped to a domain) from the user identified by userId - the inverse of grant-user-permission. Does not touch whatever the user's sub-role still grants them separately.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:revoke`  
**Middlewares:** auth:admin  
**Requires `command` field:** true

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `:userId` | path | string | true | required |
| `data.obj` | body | string | true | required |
| `data.act` | body | string | true | required |
| `data.domain` | body | string | false | omitempty |

```json
{
  "command": "revoke-user-permission",
  "data": {
    "act": "example_string",
    "domain": "example_string",
    "obj": "example_string"
  }
}
```

---

#### `POST /v2/admin/register` - register-admin

Registers a sub-account (email/password/name/phone/sub_role_code) below super_admin - sub_role_code 'super_admin' is rejected outright, that account already exists per merchant/system. parent_role is never sent by the client, it's auto-detected from the caller: a system_owner caller may pass merchant_id to pick which merchant the new admin belongs to, a merchant caller always gets their own merchant_id forced server-side regardless of what's in the request body. Callable by both system_owner:super_admin and merchant:super_admin.

**Tags:** admin  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `admin:register`  
**Middlewares:** auth:admin  
**Requires `command` field:** true

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `data.email` | body | string | true | required,email |
| `data.password` | body | string | true | required,min=8 |
| `data.name` | body | string | true | required |
| `data.phone` | body | string | false | omitempty |
| `data.sub_role_code` | body | string | true | required |
| `data.merchant_id` | body | string | false | omitempty |

```json
{
  "command": "register-admin",
  "data": {
    "email": "example_string",
    "merchant_id": "example_string",
    "name": "example_string",
    "password": "example_string",
    "phone": "example_string",
    "sub_role_code": "example_string"
  }
}
```

---

## Queries (4)

### admin

#### `GET /v2/admin/permissions/audit-log` - get-permission-audit

Lists the audit trail of permission changes - grants/revokes against roles or users, plus catalog lifecycle events (permissions appearing/disappearing as code changes) - each entry recording who performed it, when, and from which IP. Optionally filter by target_type (role/user/permission), target_id, and/or action; all filters are optional and combine as AND. Paginated.

paginated

**Tags:** admin, permission, audit  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:read`  
**Middlewares:** auth:admin  
**Requires `command` field:** false

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `target_type` | query | string | false | omitempty,oneof=role user permission |
| `target_id` | query | string | false | omitempty |
| `action` | query | string | false | omitempty |
| `page` | query | integer | false | min=1 |
| `page_size` | query | integer | false | min=1,max=100 |

```json
{
  "query": "get-permission-audit"
}
```

---

#### `GET /v2/admin/permissions/catalog` - get-permission-catalog

Lists the full permission catalog (obj/act pairs with their label/group_name/is_active), auto-synced from the routes/permissions declared in code rather than hand-maintained - this is the source list an admin UI would pull from when building a grant/revoke picker. No filters, always returns everything.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:read`  
**Middlewares:** auth:admin  
**Requires `command` field:** false

```json
{
  "query": "get-permission-catalog"
}
```

---

#### `GET /v2/admin/permissions/roles/:subRoleCode` - get-role-permissions

Lists the permissions currently granted to a sub-role (subRoleCode), scoped to a domain - merchant callers are always forced to their own merchant_id (same rule as grant/revoke-role-permission), system_owner callers may pass ?domain= to inspect a specific merchant's sub-role or omit it for the global domain. This is the missing counterpart to grant/revoke-role-permission: it lets an admin UI render the current checked/unchecked state of a grant picker instead of only being able to fire grant/revoke blind. Root sub-roles (is_root=true, e.g. super_admin) bypass Casbin entirely at enforcement time, so this reports the full active catalog with is_root=true rather than whatever few rows happen to sit in casbin_rules for it.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:read`  
**Middlewares:** auth:admin  
**Requires `command` field:** false

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `:subRoleCode` | path | string | true | required |
| `domain` | query | string | false | omitempty |

```json
{
  "query": "get-role-permissions"
}
```

---

#### `GET /v2/admin/permissions/users/:userId` - get-user-permissions

Returns a target user's full effective permission set - role-inherited grants plus any per-user grant/deny override combined - using the same Enforce()-per-catalog-entry logic as GET /profile/permissions (internal/rbac.EffectivePermissions), so denied overrides are correctly excluded rather than showing up as granted. Distinct from /profile/permissions, which only ever answers for the caller themselves - this is the admin-facing lookup for managing someone else's access.

**Tags:** admin, permission  
**Auth:** required - `Authorization: Bearer <token>` (system_owner or merchant)  
**Permission:** `permission:read`  
**Middlewares:** auth:admin  
**Requires `command` field:** false

| Field | Source | Type | Required | Validation |
|---|---|---|---|---|
| `:userId` | path | string | true | required |
| `domain` | query | string | false | omitempty |

```json
{
  "query": "get-user-permissions"
}
```

---

