# Admin API

Source: https://docs.externa.qiick.io/docs/admin-api

Admin routes live in `routes/admin.php`. All require `auth` + `verified`. Resource actions use `permission:{PermissionEnum}`; the file manager uses `can.manage.files`, which maps route name / HTTP method / bulk `action` to a file permission.

> **Client**
> These are same-origin session endpoints for the Inertia SPA (and occasional JSON fetches), not a public API. See [Routing overview](/docs/routing).

## Users

| Method   | Path                    | Route name           | Permission                                                        |
| -------- | ----------------------- | -------------------- | ----------------------------------------------------------------- |
| `GET`    | `/users`                | `users.index`        | `can-show-users`                                                  |
| `POST`   | `/users`                | `users.store`        | `can-create-users`                                                |
| `PUT`    | `/users/{user}`         | `users.update`       | `can-edit-users`                                                  |
| `DELETE` | `/users/{user}`         | `users.destroy`      | `can-delete-users`                                                |
| `POST`   | `/users/{user}/restore` | `users.restore`      | `can-restore-users`                                               |
| `DELETE` | `/users/{user}/force`   | `users.force-delete` | `can-force-delete-users`                                          |
| `POST`   | `/users/bulk-actions`   | `users.bulk-actions` | Enforced in controller per action (not a single route middleware) |

Controller: `App\Http\Controllers\Admin\UserController`.

## Groups

| Method   | Path                      | Route name            | Permission                                                                 |
| -------- | ------------------------- | --------------------- | -------------------------------------------------------------------------- |
| `GET`    | `/groups`                 | `groups.index`        | `can-show-groups`                                                          |
| `POST`   | `/groups`                 | `groups.store`        | `can-create-groups`                                                        |
| `PUT`    | `/groups/{group}`         | `groups.update`       | `can-edit-groups`                                                          |
| `DELETE` | `/groups/{group}`         | `groups.destroy`      | `can-delete-groups`                                                        |
| `POST`   | `/groups/{group}/restore` | `groups.restore`      | `can-restore-groups`                                                       |
| `DELETE` | `/groups/{group}/force`   | `groups.force-delete` | `can-force-delete-groups`                                                  |
| `POST`   | `/groups/bulk-actions`    | `groups.bulk-actions` | Enforced in FormRequest per action (`delete` / `restore` / `force_delete`) |

Controller: `UserGroupController`. Soft-delete lifecycle matches users (trashed filter on index, restore, force-delete, bulk). Groups inherit role permissions and role-tied collection/file ACL — see [Users & groups](/docs/users-and-groups).

## Roles

| Method   | Path                           | Route name           | Permission                        |
| -------- | ------------------------------ | -------------------- | --------------------------------- |
| `GET`    | `/settings/roles`              | `roles.index`        | `can-show-roles`                  |
| `GET`    | `/settings/roles/create`       | `roles.create`       | `can-create-roles`                |
| `POST`   | `/settings/roles`              | `roles.store`        | `can-create-roles`                |
| `GET`    | `/settings/roles/{role}/edit`  | `roles.edit`         | `can-edit-roles`                  |
| `PUT`    | `/settings/roles/{role}`       | `roles.update`       | `can-edit-roles`                  |
| `DELETE` | `/settings/roles/{role}`       | `roles.destroy`      | `can-delete-roles`                |
| `POST`   | `/settings/roles/bulk-actions` | `roles.bulk-actions` | Enforced in controller per action |

Controller: `RoleController`. Create/edit are Inertia form pages; index/store/update/destroy also serve the SPA. Role forms also sync **Collection access** for the [Public CMS API](/docs/public-cms-api).

## API keys

| Method   | Path                          | Route name         | Permission            |
| -------- | ----------------------------- | ------------------ | --------------------- |
| `GET`    | `/settings/api-keys`          | `api-keys.index`   | `can-show-api-keys`   |
| `POST`   | `/settings/api-keys`          | `api-keys.store`   | `can-manage-api-keys` |
| `DELETE` | `/settings/api-keys/{apiKey}` | `api-keys.destroy` | `can-manage-api-keys` |

Controller: `ApiKeyController`. Secrets (`ek_…`) are shown once after create. Keys authenticate `/api/v1` — see [Public CMS API](/docs/public-cms-api).

## Permissions (+ sync)

| Method   | Path                                 | Route name            | Permission               |
| -------- | ------------------------------------ | --------------------- | ------------------------ |
| `GET`    | `/settings/permissions`              | `permissions.index`   | `can-show-permissions`   |
| `POST`   | `/settings/permissions`              | `permissions.store`   | `can-create-permissions` |
| `PUT`    | `/settings/permissions/{permission}` | `permissions.update`  | `can-edit-permissions`   |
| `DELETE` | `/settings/permissions/{permission}` | `permissions.destroy` | `can-delete-permissions` |
| `POST`   | `/settings/permissions/sync`         | `permissions.sync`    | `can-edit-permissions`   |

`POST /settings/permissions/sync` runs `php artisan permissions:sync` (optional prune from the request body / controller). Prefer enum-driven sync over ad-hoc DB permission rows — see [Effective permissions](/docs/permissions) and [Extending Externa](/docs/extending).

## Activity logs

| Method | Path             | Route name            | Permission               |
| ------ | ---------------- | --------------------- | ------------------------ |
| `GET`  | `/activity-logs` | `activity-logs.index` | `can-show-activity-logs` |

Controller: `ActivityLogController` (Spatie activity log UI).

## Jobs monitor

Defined in `routes/admin.php` under `/settings/jobs`:

| Method   | Path                          | Route name     | Permission        |
| -------- | ----------------------------- | -------------- | ----------------- |
| `GET`    | `/settings/jobs`              | `jobs.index`   | `can-show-jobs`   |
| `POST`   | `/settings/jobs/{uuid}/retry` | `jobs.retry`   | `can-manage-jobs` |
| `DELETE` | `/settings/jobs/{uuid}`       | `jobs.destroy` | `can-manage-jobs` |

Product notes: [Operations — Jobs monitor](/docs/operations#jobs-monitor-admin-ui).

## Project settings (related)

Project + branding HTTP live in `routes/settings.php` (not `admin.php`) and require `can-manage-project-settings`. From 1.3.0 the surface is split:

| Method      | Path                                                                       | Name (selection)                                        |
| ----------- | -------------------------------------------------------------------------- | ------------------------------------------------------- |
| `GET`/`PUT` | `/settings/project`                                                        | `project.edit` / `project.update` (General)             |
| `GET`/`PUT` | `/settings/project/{localization,security,content,files,api,integrations}` | `project.*.edit` / `project.*.update`                   |
| `POST`      | `/settings/project/integrations/webhook-test`                              | `project.integrations.webhook-test`                     |
| `POST`      | `/settings/project/webhook-test`                                           | `project.webhook-test` (deprecated alias, one minor)    |
| `GET`/`PUT` | `/settings/appearance`                                                     | `appearance.edit` / `appearance.update`                 |
| `GET`/`PUT` | `/settings/appearance/sidebar`                                             | `appearance.sidebar.edit` / `appearance.sidebar.update` |

Full guide: [Project settings](/docs/project-settings).

---

## Files (`can.manage.files`)

All routes below sit in:

```php
Route::middleware(['auth', 'verified'])->group(...)
Route::middleware('can.manage.files')->group(...)
```

Prefix: `/files`, name prefix: `files.`.

### How permissions are chosen

`EnsureCanManageFiles` resolves a `PermissionEnum` then checks `EffectivePermissionResolver`:

| Route name / case                                              | Permission                           |
| -------------------------------------------------------------- | ------------------------------------ |
| `files.restore`                                                | `can-restore-files`                  |
| `files.force-delete`                                           | `can-force-delete-files`             |
| `files.download`, `files.download-many`, `files.zips.download` | `can-download-files`                 |
| `files.favorite`, `files.unfavorite`                           | `can-favorite-files`                 |
| `files.copy`                                                   | `can-copy-files`                     |
| `files.replace`                                                | `can-replace-files`                  |
| `files.tags`                                                   | `can-tag-files`                      |
| `files.update`                                                 | `can-update-file-metadata`           |
| `files.bulk`                                                   | Depends on JSON `action` (see below) |
| Other `GET`/`HEAD`                                             | `can-show-files`                     |
| Other `POST`                                                   | `can-create-files`                   |
| Other `PUT`/`PATCH`                                            | `can-edit-files`                     |
| Other `DELETE`                                                 | `can-delete-files`                   |

**Bulk `action` → permission:**

| `action`                 | Permission               |
| ------------------------ | ------------------------ |
| `restore`                | `can-restore-files`      |
| `force_delete`           | `can-force-delete-files` |
| `favorite`, `unfavorite` | `can-favorite-files`     |
| `tag`, `untag`           | `can-tag-files`          |
| `copy`                   | `can-copy-files`         |
| `delete`                 | `can-delete-files`       |
| `move`                   | `can-edit-files`         |
| _(default)_              | `can-edit-files`         |

### List, browse, tags

| Method | Path               | Route name         | Typical permission                        |
| ------ | ------------------ | ------------------ | ----------------------------------------- |
| `GET`  | `/files/list`      | `files.list`       | `can-show-files`                          |
| `GET`  | `/files/tags`      | `files.tags.index` | `can-show-files`                          |
| `GET`  | `/files/{folder?}` | `files.index`      | `can-show-files` (folder must be numeric) |

`files.index` is the Inertia file-manager page. Static `/files/*` paths are registered **before** `{folder}` so names like `list` are not treated as folder IDs.

### Upload and chunked upload

| Method | Path                      | Route name               | Typical permission |
| ------ | ------------------------- | ------------------------ | ------------------ |
| `POST` | `/files/upload`           | `files.upload`           | `can-create-files` |
| `POST` | `/files/folders`          | `files.folders.store`    | `can-create-files` |
| `POST` | `/files/uploads/init`     | `files.uploads.init`     | `can-create-files` |
| `POST` | `/files/uploads/chunk`    | `files.uploads.chunk`    | `can-create-files` |
| `POST` | `/files/uploads/complete` | `files.uploads.complete` | `can-create-files` |
| `GET`  | `/files/uploads/status`   | `files.uploads.status`   | `can-show-files`   |

Stale incomplete uploads are cleaned by `files:cleanup-uploads` ([Files configuration](/docs/files-configuration)).

Upload / replace / chunked init+complete / rename reject dangerous extensions via `ForbiddenUploadExtension` (validation error). Name, metadata strings, and tags are HTML-stripped (`PlainTextSanitizer`). See [File manager — Upload security](/docs/file-manager#upload-security).

### Bulk, download, zip

| Method | Path                  | Route name            | Permission notes           |
| ------ | --------------------- | --------------------- | -------------------------- |
| `POST` | `/files/bulk`         | `files.bulk`          | Per `action` (table above) |
| `POST` | `/files/download`     | `files.download-many` | `can-download-files`       |
| `GET`  | `/files/zips/{jobId}` | `files.zips.download` | `can-download-files`       |

Multi-file/folder zips are prepared asynchronously; expired archives are removed by `files:cleanup-zips`.

### Single-file mutations

| Method   | Path                       | Route name           | Permission                        |
| -------- | -------------------------- | -------------------- | --------------------------------- |
| `PATCH`  | `/files/{file}`            | `files.update`       | `can-update-file-metadata`        |
| `POST`   | `/files/{file}/replace`    | `files.replace`      | `can-replace-files`               |
| `POST`   | `/files/{file}/copy`       | `files.copy`         | `can-copy-files`                  |
| `POST`   | `/files/{file}/favorite`   | `files.favorite`     | `can-favorite-files`              |
| `DELETE` | `/files/{file}/favorite`   | `files.unfavorite`   | `can-favorite-files`              |
| `PUT`    | `/files/{file}/tags`       | `files.tags`         | `can-tag-files`                   |
| `GET`    | `/files/{file}/download`   | `files.download`     | `can-download-files`              |
| `GET`    | `/files/{file}/thumbnail`  | `files.thumbnail`    | `can-show-files`                  |
| `GET`    | `/files/{file}/where-used` | `files.where-used`   | `can-show-files`                  |
| `PATCH`  | `/files/{file}/move`       | `files.move`         | `can-edit-files`                  |
| `PATCH`  | `/files/{file}/rename`     | `files.rename`       | `can-edit-files`                  |
| `DELETE` | `/files/{file}`            | `files.destroy`      | `can-delete-files`                |
| `POST`   | `/files/{file}/restore`    | `files.restore`      | `can-restore-files`               |
| `DELETE` | `/files/{file}/force`      | `files.force-delete` | `can-force-delete-files`          |
| `POST`   | `/files/{file}/attach`     | `files.attach`       | `can-create-files` (POST default) |
| `POST`   | `/files/{file}/detach`     | `files.detach`       | `can-create-files` (POST default) |

Controller: `App\Http\Controllers\Admin\FileController`. Storage/threshold config: [Files configuration](/docs/files-configuration). Tree model: [File tree model](/docs/file-tree-model).
