fix(ci): add missing .dockerignore entries, replace uv audit with uvx pip-audit, upgrade trivy-action

This commit is contained in:
Jacob Magar 2026-04-05 08:17:11 -04:00
parent d30739789d
commit c39b05277c
25 changed files with 27501 additions and 2998 deletions

View file

@ -21,10 +21,11 @@
.env.*
!.env.example
*.log
logs/
backups/
docs/
specs/
logs
backups
docs
scripts
specs
*.md
!README.md
__pycache__
@ -45,5 +46,5 @@ biome.json
.pre-commit-config.yaml
.prettierrc
.prettierignore
tests/
tests

View file

@ -95,8 +95,10 @@ jobs:
- uses: astral-sh/setup-uv@v5
with:
version: "0.9.25"
- name: Install dependencies
run: uv sync
- name: Dependency audit
run: uv audit
run: uvx pip-audit
docker-security:
name: Docker Security

View file

@ -66,7 +66,7 @@ jobs:
- name: Scan image for vulnerabilities
if: github.event_name != 'pull_request'
uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # v0.28.0
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'

View file

@ -202,8 +202,8 @@ uv run pytest -x # Fail fast on first error
See `tests/mcporter/README.md` for transport differences and `docs/DESTRUCTIVE_ACTIONS.md` for exact destructive-action test commands.
### API Reference Docs
- `docs/UNRAID_API_COMPLETE_REFERENCE.md` — Full GraphQL schema reference
- `docs/UNRAID_API_OPERATIONS.md` — All supported operations with examples
- `docs/unraid/UNRAID-API-SUMMARY.md` — Condensed schema overview
- `docs/unraid/UNRAID-API-COMPLETE-REFERENCE.md` — Full GraphQL schema reference
- `docs/MARKETPLACE.md` — Plugin marketplace listing and publishing guide
- `docs/PUBLISHING.md` — Step-by-step instructions for publishing to Claude plugin registry
@ -298,4 +298,4 @@ Bump type is determined by the commit message prefix:
- `CHANGELOG.md` — new entry under the bumped version
All files MUST have the same version. Never bump only one file.
CHANGELOG.md must have an entry for every version bump.
CHANGELOG.md must have an entry for every version bump.

View file

@ -93,7 +93,7 @@ Complete listing of all plugin components.
| `scripts/check-no-baked-env.sh` | Verify no env vars baked into images |
| `scripts/check-outdated-deps.sh` | Dependency freshness check |
| `scripts/ensure-ignore-files.sh` | Gitignore/dockerignore alignment |
| `scripts/generate_unraid_api_reference.py` | Generate API reference from GraphQL introspection |
| `scripts/generate_unraid_api_reference.py` | Generate canonical API docs and schema change report from GraphQL introspection |
| `scripts/lint-plugin.sh` | Plugin manifest validation |
| `scripts/validate-marketplace.sh` | Marketplace JSON validation |

View file

@ -1,290 +0,0 @@
# Unraid GraphQL API Operations
Generated via live introspection at `2026-02-15 23:45:50Z`.
## Schema Summary
- Query root: `Query`
- Mutation root: `Mutation`
- Subscription root: `Subscription`
- Total types: **164**
- Total directives: **6**
- Type kinds:
- `ENUM`: 32
- `INPUT_OBJECT`: 16
- `INTERFACE`: 2
- `OBJECT`: 103
- `SCALAR`: 10
- `UNION`: 1
## Queries
Total: **46**
### `apiKey(id: PrefixedID!): ApiKey`
#### Required Permissions: - Action: **READ_ANY** - Resource: **API_KEY**
Arguments:
- `id`: `PrefixedID!`
### `apiKeyPossiblePermissions(): [Permission!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **PERMISSION** #### Description: All possible permissions for API keys
### `apiKeyPossibleRoles(): [Role!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **PERMISSION** #### Description: All possible roles for API keys
### `apiKeys(): [ApiKey!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **API_KEY**
### `array(): UnraidArray!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **ARRAY**
### `config(): Config!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CONFIG**
### `customization(): Customization`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CUSTOMIZATIONS**
### `disk(id: PrefixedID!): Disk!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **DISK**
Arguments:
- `id`: `PrefixedID!`
### `disks(): [Disk!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **DISK**
### `docker(): Docker!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **DOCKER**
### `flash(): Flash!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **FLASH**
### `getApiKeyCreationFormSchema(): ApiKeyFormSettings!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **API_KEY** #### Description: Get JSON Schema for API key creation form
### `getAvailableAuthActions(): [AuthAction!]!`
Get all available authentication actions with possession
### `getPermissionsForRoles(roles: [Role!]!): [Permission!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **PERMISSION** #### Description: Get the actual permissions that would be granted by a set of roles
Arguments:
- `roles`: `[Role!]!`
### `info(): Info!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **INFO**
### `isInitialSetup(): Boolean!`
### `isSSOEnabled(): Boolean!`
### `logFile(lines: Int, path: String!, startLine: Int): LogFileContent!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **LOGS**
Arguments:
- `lines`: `Int`
- `path`: `String!`
- `startLine`: `Int`
### `logFiles(): [LogFile!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **LOGS**
### `me(): UserAccount!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **ME**
### `metrics(): Metrics!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **INFO**
### `notifications(): Notifications!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **NOTIFICATIONS** #### Description: Get all notifications
### `oidcConfiguration(): OidcConfiguration!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CONFIG** #### Description: Get the full OIDC configuration (admin only)
### `oidcProvider(id: PrefixedID!): OidcProvider`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CONFIG** #### Description: Get a specific OIDC provider by ID
Arguments:
- `id`: `PrefixedID!`
### `oidcProviders(): [OidcProvider!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CONFIG** #### Description: Get all configured OIDC providers (admin only)
### `online(): Boolean!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **ONLINE**
### `owner(): Owner!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **OWNER**
### `parityHistory(): [ParityCheck!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **ARRAY**
### `plugins(): [Plugin!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CONFIG** #### Description: List all installed plugins with their metadata
### `previewEffectivePermissions(permissions: [AddPermissionInput!], roles: [Role!]): [Permission!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **PERMISSION** #### Description: Preview the effective permissions for a combination of roles and explicit permissions
Arguments:
- `permissions`: `[AddPermissionInput!]`
- `roles`: `[Role!]`
### `publicOidcProviders(): [PublicOidcProvider!]!`
Get public OIDC provider information for login buttons
### `publicPartnerInfo(): PublicPartnerInfo`
### `publicTheme(): Theme!`
### `rclone(): RCloneBackupSettings!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **FLASH**
### `registration(): Registration`
#### Required Permissions: - Action: **READ_ANY** - Resource: **REGISTRATION**
### `server(): Server`
#### Required Permissions: - Action: **READ_ANY** - Resource: **SERVERS**
### `servers(): [Server!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **SERVERS**
### `services(): [Service!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **SERVICES**
### `settings(): Settings!`
### `shares(): [Share!]!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **SHARE**
### `upsConfiguration(): UPSConfiguration!`
### `upsDeviceById(id: String!): UPSDevice`
Arguments:
- `id`: `String!`
### `upsDevices(): [UPSDevice!]!`
### `validateOidcSession(token: String!): OidcSessionValidation!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **CONFIG** #### Description: Validate an OIDC session token (internal use for CLI validation)
Arguments:
- `token`: `String!`
### `vars(): Vars!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **VARS**
### `vms(): Vms!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **VMS** #### Description: Get information about all VMs on the system
## Mutations
Total: **22**
### `addPlugin(input: PluginManagementInput!): Boolean!`
#### Required Permissions: - Action: **UPDATE_ANY** - Resource: **CONFIG** #### Description: Add one or more plugins to the API. Returns false if restart was triggered automatically, true if manual restart is required.
Arguments:
- `input`: `PluginManagementInput!`
### `apiKey(): ApiKeyMutations!`
### `archiveAll(importance: NotificationImportance): NotificationOverview!`
Arguments:
- `importance`: `NotificationImportance`
### `archiveNotification(id: PrefixedID!): Notification!`
Marks a notification as archived.
Arguments:
- `id`: `PrefixedID!`
### `archiveNotifications(ids: [PrefixedID!]!): NotificationOverview!`
Arguments:
- `ids`: `[PrefixedID!]!`
### `array(): ArrayMutations!`
### `configureUps(config: UPSConfigInput!): Boolean!`
Arguments:
- `config`: `UPSConfigInput!`
### `createNotification(input: NotificationData!): Notification!`
Creates a new notification record
Arguments:
- `input`: `NotificationData!`
### `customization(): CustomizationMutations!`
### `deleteArchivedNotifications(): NotificationOverview!`
Deletes all archived notifications on server.
### `deleteNotification(id: PrefixedID!, type: NotificationType!): NotificationOverview!`
Arguments:
- `id`: `PrefixedID!`
- `type`: `NotificationType!`
### `docker(): DockerMutations!`
### `initiateFlashBackup(input: InitiateFlashBackupInput!): FlashBackupStatus!`
Initiates a flash drive backup using a configured remote.
Arguments:
- `input`: `InitiateFlashBackupInput!`
### `parityCheck(): ParityCheckMutations!`
### `rclone(): RCloneMutations!`
### `recalculateOverview(): NotificationOverview!`
Reads each notification to recompute & update the overview.
### `removePlugin(input: PluginManagementInput!): Boolean!`
#### Required Permissions: - Action: **DELETE_ANY** - Resource: **CONFIG** #### Description: Remove one or more plugins from the API. Returns false if restart was triggered automatically, true if manual restart is required.
Arguments:
- `input`: `PluginManagementInput!`
### `unarchiveAll(importance: NotificationImportance): NotificationOverview!`
Arguments:
- `importance`: `NotificationImportance`
### `unarchiveNotifications(ids: [PrefixedID!]!): NotificationOverview!`
Arguments:
- `ids`: `[PrefixedID!]!`
### `unreadNotification(id: PrefixedID!): Notification!`
Marks a notification as unread.
Arguments:
- `id`: `PrefixedID!`
### `updateSettings(input: JSON!): UpdateSettingsResponse!`
#### Required Permissions: - Action: **UPDATE_ANY** - Resource: **CONFIG**
Arguments:
- `input`: `JSON!`
### `vm(): VmMutations!`
## Subscriptions
Total: **11**
### `arraySubscription(): UnraidArray!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **ARRAY**
### `logFile(path: String!): LogFileContent!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **LOGS**
Arguments:
- `path`: `String!`
### `notificationAdded(): Notification!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **NOTIFICATIONS**
### `notificationsOverview(): NotificationOverview!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **NOTIFICATIONS**
### `ownerSubscription(): Owner!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **OWNER**
### `parityHistorySubscription(): ParityCheck!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **ARRAY**
### `serversSubscription(): Server!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **SERVERS**
### `systemMetricsCpu(): CpuUtilization!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **INFO**
### `systemMetricsCpuTelemetry(): CpuPackages!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **INFO**
### `systemMetricsMemory(): MemoryUtilization!`
#### Required Permissions: - Action: **READ_ANY** - Resource: **INFO**
### `upsUpdates(): UPSDevice!`

View file

@ -1,928 +0,0 @@
# Unraid API v4.29.2 — Complete Reference
> **Source of truth.** Auto-generated from live GraphQL introspection against tootie (10.1.0.2:31337) on 2026-03-15.
> Unraid 7.2.4 · API v4.29.2 · 46 queries · 22 mutations · 11 subscriptions · 156 types
---
## Table of Contents
- [Authentication](#authentication)
- [Scalars & ID Format](#scalars--id-format)
- [Queries](#queries)
- [System & Server Info](#system--server-info)
- [Array & Storage](#array--storage)
- [Docker](#docker)
- [Virtual Machines](#virtual-machines)
- [Notifications](#notifications)
- [API Keys & Permissions](#api-keys--permissions)
- [Users & Auth](#users--auth)
- [RClone / Backup](#rclone--backup)
- [UPS / Power](#ups--power)
- [Settings & Configuration](#settings--configuration)
- [Logs](#logs)
- [OIDC / SSO](#oidc--sso)
- [Plugins](#plugins)
- [Mutations](#mutations)
- [Notification Mutations](#notification-mutations)
- [Array Mutations](#array-mutations)
- [Docker Mutations](#docker-mutations)
- [VM Mutations](#vm-mutations)
- [Parity Check Mutations](#parity-check-mutations)
- [API Key Mutations](#api-key-mutations)
- [Customization Mutations](#customization-mutations)
- [RClone Mutations](#rclone-mutations)
- [Flash Backup](#flash-backup)
- [Settings Mutations](#settings-mutations)
- [Plugin Mutations](#plugin-mutations)
- [Subscriptions](#subscriptions)
- [Enums](#enums)
- [Input Types](#input-types)
- [Object Types (Full Field Reference)](#object-types-full-field-reference)
---
## Authentication
All requests require an API key passed via the `x-api-key` HTTP header:
```bash
curl -k -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ online }"}' \
https://YOUR-SERVER/graphql
```
**Rate limit:** 100 requests per 10 seconds.
---
## Scalars & ID Format
| Scalar | Description |
|--------|-------------|
| `PrefixedID` | Server-prefixed ID: `<serverHash>:<localId>`. Input accepts with or without prefix. Output always includes prefix. |
| `BigInt` | Non-fractional signed whole numbers (for disk sizes in KB, memory in bytes, etc.) |
| `DateTime` | ISO 8601 UTC string, e.g. `2026-03-15T09:54:33Z` |
| `JSON` | Arbitrary JSON value (used for settings, labels, mount info) |
| `Port` | Valid TCP port 065535 |
---
## Queries
### System & Server Info
#### `info``Info!`
Full hardware and software information. Permission: `READ_ANY` on `INFO`.
```graphql
query {
info {
time
baseboard { manufacturer model version serial memMax memSlots }
cpu { manufacturer brand cores threads speed speedmax socket topology packages { totalPower power temp } }
devices {
gpu { type vendorname productid blacklisted }
network { iface model vendor mac speed dhcp }
pci { type vendorname productname vendorid productid }
usb { name bus device }
}
display { theme unit scale tabs resize wwn total usage text warning critical hot max locale
case { url icon error base64 }
}
memory { layout { size bank type clockSpeed manufacturer formFactor partNum serialNum } }
os { platform distro release kernel arch hostname fqdn uptime uefi }
system { manufacturer model version serial uuid sku virtual }
versions { core { unraid api kernel } packages { openssl node npm pm2 git nginx php docker } }
}
}
```
#### `vars``Vars!`
143 system variables including hostname, timezone, array config, share settings, registration state, CSRF token, disk sync parameters, and much more. Permission: `READ_ANY` on `VARS`.
```graphql
query {
vars {
version name timeZone comment security workgroup
port portssl portssh useSsl useSsh useTelnet
startArray spindownDelay shutdownTimeout
shareCount shareSmbCount shareNfsCount
regTy regState regTo
mdNumDisks mdNumDisabled mdState mdResync
configValid configError safeMode csrfToken
}
}
```
#### `metrics``Metrics!`
CPU and memory utilization. Permission: `READ_ANY` on `INFO`.
```graphql
query {
metrics {
cpu { percentTotal cpus { percentTotal percentUser percentSystem percentIdle } }
memory { total used free available active buffcache percentTotal swapTotal swapUsed swapFree percentSwapTotal }
}
}
```
#### `server``Server`
Local server info. Permission: `READ_ANY` on `SERVERS`.
```graphql
query {
server { id name status guid apikey wanip lanip localurl remoteurl owner { username url avatar } }
}
```
#### `servers``[Server!]!`
All registered servers (usually just the local one). Permission: `READ_ANY` on `SERVERS`.
#### `online``Boolean!`
Simple connectivity check. Permission: `READ_ANY` on `ONLINE`.
#### `owner``Owner!`
Server owner info. Permission: `READ_ANY` on `OWNER`. Returns `username`, `url`, `avatar`.
#### `registration``Registration`
License info. Permission: `READ_ANY` on `REGISTRATION`. Returns `type`, `state`, `keyFile { location contents }`, `expiration`, `updateExpiration`.
#### `config``Config!`
Configuration validity. Permission: `READ_ANY` on `CONFIG`. Returns `valid`, `error`.
#### `services``[Service!]!`
Running services. Permission: `READ_ANY` on `SERVICES`. Each: `name`, `online`, `uptime { timestamp }`, `version`.
#### `flash``Flash!`
Flash drive info. Permission: `READ_ANY` on `FLASH`. Returns `guid`, `vendor`, `product`.
#### `customization``Customization`
UI customization. Permission: `READ_ANY` on `CUSTOMIZATIONS`. Returns `activationCode { ... }`, `partnerInfo { ... }`, `theme { ... }`.
#### `settings``Settings!`
All settings including unified form, SSO, and API config.
```graphql
query {
settings {
unified { dataSchema uiSchema values }
sso { oidcProviders { id name clientId issuer scopes } }
api { version extraOrigins sandbox plugins }
}
}
```
#### `isInitialSetup``Boolean!`
Whether server is in initial setup mode (no permission required).
---
### Array & Storage
#### `array``UnraidArray!`
Array state with all disks. Permission: `READ_ANY` on `ARRAY`.
```graphql
query {
array {
state
capacity { kilobytes { free used total } disks { free used total } }
boot { id name device size status temp type }
parities { id name device size status temp numReads numWrites numErrors }
parityCheckStatus { status progress speed errors duration correcting paused running }
disks { id idx name device size status temp fsSize fsFree fsUsed type fsType color isSpinning numReads numWrites numErrors }
caches { id name device size status temp fsSize fsFree fsUsed type }
}
}
```
#### `shares``[Share!]!`
User shares. Permission: `READ_ANY` on `SHARE`.
```graphql
query {
shares { id name free used size include exclude cache nameOrig comment allocator splitLevel floor cow color luksStatus }
}
```
#### `disks``[Disk!]!`
Physical disks (hardware-level). Permission: `READ_ANY` on `DISK`.
```graphql
query {
disks { id device type name vendor size serialNum firmwareRevision interfaceType smartStatus temperature isSpinning
partitions { name fsType size } }
}
```
#### `disk(id: PrefixedID!)``Disk!`
Single disk by ID. Permission: `READ_ANY` on `DISK`.
#### `parityHistory``[ParityCheck!]!`
Parity check history. Permission: `READ_ANY` on `ARRAY`.
```graphql
query {
parityHistory { date duration speed status errors progress correcting paused running }
}
```
---
### Docker
#### `docker``Docker!`
Container and network queries. Permission: `READ_ANY` on `DOCKER`.
**Available sub-fields on Docker type:**
- `containers(skipCache: Boolean! = false)``[DockerContainer!]!`
- `networks(skipCache: Boolean! = false)``[DockerNetwork!]!`
**That's it.** No `logs`, no `portConflicts`, no `containerUpdateStatuses`. Only `containers` and `networks`.
```graphql
query {
docker {
containers(skipCache: false) {
id names image imageId command created state status autoStart
ports { ip privatePort publicPort type }
sizeRootFs labels hostConfig { networkMode } networkSettings mounts
}
networks(skipCache: false) {
id name created scope driver enableIPv6
internal attachable ingress configOnly
ipam containers options labels
}
}
}
```
**DockerContainer fields:** `id`, `names`, `image`, `imageId`, `command`, `created`, `ports`, `sizeRootFs`, `labels`, `state` (RUNNING/EXITED), `status`, `hostConfig`, `networkSettings`, `mounts`, `autoStart`.
**DockerNetwork fields:** `id`, `name`, `created`, `scope`, `driver`, `enableIPv6`, `ipam`, `internal`, `attachable`, `ingress`, `configFrom`, `configOnly`, `containers`, `options`, `labels`.
---
### Virtual Machines
#### `vms``Vms!`
All VMs. Permission: `READ_ANY` on `VMS`.
```graphql
query {
vms {
domains { id name state uuid }
domain { id name state }
}
}
```
**VmState enum:** `NOSTATE`, `RUNNING`, `IDLE`, `PAUSED`, `SHUTDOWN`, `SHUTOFF`, `CRASHED`, `PMSUSPENDED`.
---
### Notifications
#### `notifications``Notifications!`
Overview and list. Permission: `READ_ANY` on `NOTIFICATIONS`.
```graphql
# Overview (counts by severity)
query {
notifications {
overview {
unread { info warning alert total }
archive { info warning alert total }
}
}
}
# List with filter
query {
notifications {
list(filter: { type: UNREAD, offset: 0, limit: 20, importance: WARNING }) {
id title subject description importance link type timestamp formattedTimestamp
}
}
}
```
**NotificationFilter input:** `type` (UNREAD/ARCHIVE, required), `offset` (required), `limit` (required), `importance` (optional: INFO/WARNING/ALERT).
---
### API Keys & Permissions
#### `apiKeys``[ApiKey!]!`
All API keys. Permission: `READ_ANY` on `API_KEY`.
```graphql
query { apiKeys { id key name description roles createdAt permissions { resource actions } } }
```
#### `apiKey(id: PrefixedID!)``ApiKey`
Single API key by ID. Permission: `READ_ANY` on `API_KEY`.
#### `apiKeyPossibleRoles``[Role!]!`
Available roles (ADMIN, CONNECT, GUEST, VIEWER). Permission: `READ_ANY` on `PERMISSION`.
#### `apiKeyPossiblePermissions``[Permission!]!`
All possible permissions. Permission: `READ_ANY` on `PERMISSION`.
#### `getPermissionsForRoles(roles: [Role!]!)``[Permission!]!`
Resolve roles to actual permissions. Permission: `READ_ANY` on `PERMISSION`.
#### `previewEffectivePermissions(roles: [Role!], permissions: [AddPermissionInput!])``[Permission!]!`
Preview effective permissions for a role/permission combo. Permission: `READ_ANY` on `PERMISSION`.
#### `getAvailableAuthActions``[AuthAction!]!`
All auth actions (CREATE_ANY, READ_OWN, etc.).
#### `getApiKeyCreationFormSchema``ApiKeyFormSettings!`
JSON Schema for API key creation form. Permission: `READ_ANY` on `API_KEY`. Returns `dataSchema`, `uiSchema`, `values`.
---
### Users & Auth
#### `me``UserAccount!`
Current authenticated user. Permission: `READ_ANY` on `ME`.
```graphql
query { me { id name description roles permissions { resource actions } } }
```
---
### RClone / Backup
#### `rclone``RCloneBackupSettings!`
RClone configuration. Permission: `READ_ANY` on `FLASH`.
```graphql
query {
rclone {
remotes { name type parameters config }
drives { name options }
configForm(formOptions: { providerType: "drive", showAdvanced: false }) {
id dataSchema uiSchema
}
}
}
```
---
### UPS / Power
#### `upsDevices``[UPSDevice!]!`
All UPS devices.
```graphql
query {
upsDevices {
id name model status
battery { chargeLevel estimatedRuntime health }
power { inputVoltage outputVoltage loadPercentage }
}
}
```
#### `upsDeviceById(id: String!)``UPSDevice`
Single UPS by ID.
#### `upsConfiguration``UPSConfiguration!`
UPS daemon configuration.
```graphql
query {
upsConfiguration {
service upsCable customUpsCable upsType device
overrideUpsCapacity batteryLevel minutes timeout killUps
nisIp netServer upsName modelName
}
}
```
---
### Logs
#### `logFiles``[LogFile!]!`
Available log files. Permission: `READ_ANY` on `LOGS`.
```graphql
query { logFiles { name path size modifiedAt } }
```
#### `logFile(path: String!, lines: Int, startLine: Int)``LogFileContent!`
Read a log file. Permission: `READ_ANY` on `LOGS`.
```graphql
query { logFile(path: "/var/log/syslog", lines: 100, startLine: 1) { path content totalLines startLine } }
```
---
### OIDC / SSO
#### `isSSOEnabled``Boolean!`
Whether SSO is enabled.
#### `publicOidcProviders``[PublicOidcProvider!]!`
Public OIDC provider info for login buttons. Returns `id`, `name`, `buttonText`, `buttonIcon`, `buttonVariant`, `buttonStyle`.
#### `oidcProviders``[OidcProvider!]!`
All configured OIDC providers (admin only). Permission: `READ_ANY` on `CONFIG`.
#### `oidcProvider(id: PrefixedID!)``OidcProvider`
Single OIDC provider by ID. Permission: `READ_ANY` on `CONFIG`.
#### `oidcConfiguration``OidcConfiguration!`
Full OIDC configuration. Permission: `READ_ANY` on `CONFIG`. Returns `providers` list and `defaultAllowedOrigins`.
#### `validateOidcSession(token: String!)``OidcSessionValidation!`
Validate an OIDC session token. Permission: `READ_ANY` on `CONFIG`. Returns `valid`, `username`.
---
### Plugins
#### `plugins``[Plugin!]!`
Installed plugins. Permission: `READ_ANY` on `CONFIG`.
```graphql
query { plugins { name version hasApiModule hasCliModule } }
```
---
## Mutations
### Notification Mutations
All notification mutations are **root-level** on the Mutation type.
| Mutation | Args | Returns | Description |
|----------|------|---------|-------------|
| `createNotification` | `input: NotificationData!` | `Notification!` | Create a new notification |
| `archiveNotification` | `id: PrefixedID!` | `Notification!` | Mark as archived |
| `archiveNotifications` | `ids: [PrefixedID!]!` | `NotificationOverview!` | Archive multiple |
| `archiveAll` | `importance: NotificationImportance` | `NotificationOverview!` | Archive all (optional filter) |
| `unreadNotification` | `id: PrefixedID!` | `Notification!` | Mark as unread |
| `unarchiveNotifications` | `ids: [PrefixedID!]!` | `NotificationOverview!` | Unarchive multiple |
| `unarchiveAll` | `importance: NotificationImportance` | `NotificationOverview!` | Unarchive all (optional filter) |
| `deleteNotification` | `id: PrefixedID!`, `type: NotificationType!` | `NotificationOverview!` | Delete one notification |
| `deleteArchivedNotifications` | — | `NotificationOverview!` | Delete ALL archived |
| `recalculateOverview` | — | `NotificationOverview!` | Recompute counts from disk |
---
### Array Mutations
Nested under `mutation { array { ... } }``ArrayMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `setState` | `input: ArrayStateInput!` | `UnraidArray!` | `UPDATE_ANY` on `ARRAY` | Start/stop array (`desiredState: START/STOP`) |
| `addDiskToArray` | `input: ArrayDiskInput!` | `UnraidArray!` | `UPDATE_ANY` on `ARRAY` | Add disk to array |
| `removeDiskFromArray` | `input: ArrayDiskInput!` | `UnraidArray!` | `UPDATE_ANY` on `ARRAY` | Remove disk (array must be stopped) |
| `mountArrayDisk` | `id: PrefixedID!` | `ArrayDisk!` | `UPDATE_ANY` on `ARRAY` | Mount a disk |
| `unmountArrayDisk` | `id: PrefixedID!` | `ArrayDisk!` | `UPDATE_ANY` on `ARRAY` | Unmount a disk |
| `clearArrayDiskStatistics` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `ARRAY` | Clear disk I/O stats |
---
### Docker Mutations
Nested under `mutation { docker { ... } }``DockerMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `start` | `id: PrefixedID!` | `DockerContainer!` | `UPDATE_ANY` on `DOCKER` | Start a container |
| `stop` | `id: PrefixedID!` | `DockerContainer!` | `UPDATE_ANY` on `DOCKER` | Stop a container |
**That's it.** No pause, unpause, remove, update, or organizer mutations exist.
---
### VM Mutations
Nested under `mutation { vm { ... } }``VmMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `start` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Start VM |
| `stop` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Graceful stop |
| `pause` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Pause VM |
| `resume` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Resume paused VM |
| `forceStop` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Force stop (hard power off) |
| `reboot` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Reboot VM |
| `reset` | `id: PrefixedID!` | `Boolean!` | `UPDATE_ANY` on `VMS` | Reset VM (hard reboot) |
---
### Parity Check Mutations
Nested under `mutation { parityCheck { ... } }``ParityCheckMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `start` | `correct: Boolean!` | `JSON!` | `UPDATE_ANY` on `ARRAY` | Start parity check (correct=true writes fixes) |
| `pause` | — | `JSON!` | `UPDATE_ANY` on `ARRAY` | Pause running check |
| `resume` | — | `JSON!` | `UPDATE_ANY` on `ARRAY` | Resume paused check |
| `cancel` | — | `JSON!` | `UPDATE_ANY` on `ARRAY` | Cancel running check |
> **Note:** Response types are `JSON!` — this API is marked WIP and types will change.
---
### API Key Mutations
Nested under `mutation { apiKey { ... } }``ApiKeyMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `create` | `input: CreateApiKeyInput!` | `ApiKey!` | `CREATE_ANY` on `API_KEY` | Create API key |
| `update` | `input: UpdateApiKeyInput!` | `ApiKey!` | `UPDATE_ANY` on `API_KEY` | Update API key |
| `delete` | `input: DeleteApiKeyInput!` | `Boolean!` | `DELETE_ANY` on `API_KEY` | Delete one or more keys |
| `addRole` | `input: AddRoleForApiKeyInput!` | `Boolean!` | `UPDATE_ANY` on `API_KEY` | Add role to key |
| `removeRole` | `input: RemoveRoleFromApiKeyInput!` | `Boolean!` | `UPDATE_ANY` on `API_KEY` | Remove role from key |
---
### Customization Mutations
Nested under `mutation { customization { ... } }``CustomizationMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `setTheme` | `theme: ThemeName!` | `Theme!` | `UPDATE_ANY` on `CUSTOMIZATIONS` | Update UI theme (azure/black/gray/white) |
---
### RClone Mutations
Nested under `mutation { rclone { ... } }``RCloneMutations!`
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `createRCloneRemote` | `input: CreateRCloneRemoteInput!` | `RCloneRemote!` | `CREATE_ANY` on `FLASH` | Create remote |
| `deleteRCloneRemote` | `input: DeleteRCloneRemoteInput!` | `Boolean!` | `DELETE_ANY` on `FLASH` | Delete remote |
---
### Flash Backup
Root-level mutation.
| Mutation | Args | Returns | Description |
|----------|------|---------|-------------|
| `initiateFlashBackup` | `input: InitiateFlashBackupInput!` | `FlashBackupStatus!` | Start flash backup to remote |
**InitiateFlashBackupInput:** `remoteName: String!`, `sourcePath: String!`, `destinationPath: String!`, `options: JSON`
Returns: `status: String!`, `jobId: String`
---
### Settings Mutations
Root-level mutations.
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `updateSettings` | `input: JSON!` | `UpdateSettingsResponse!` | `UPDATE_ANY` on `CONFIG` | Update server settings |
| `configureUps` | `config: UPSConfigInput!` | `Boolean!` | — | Configure UPS daemon |
**UpdateSettingsResponse:** `restartRequired: Boolean!`, `values: JSON!`, `warnings: [String!]`
---
### Plugin Mutations
Root-level mutations.
| Mutation | Args | Returns | Permission | Description |
|----------|------|---------|------------|-------------|
| `addPlugin` | `input: PluginManagementInput!` | `Boolean!` | `UPDATE_ANY` on `CONFIG` | Install plugin(s). Returns false if auto-restart triggered. |
| `removePlugin` | `input: PluginManagementInput!` | `Boolean!` | `DELETE_ANY` on `CONFIG` | Remove plugin(s). Returns false if auto-restart triggered. |
---
## Subscriptions
WebSocket-based real-time data (graphql-ws protocol).
| Subscription | Returns | Permission | Description |
|-------------|---------|------------|-------------|
| `notificationAdded` | `Notification!` | `READ_ANY` on `NOTIFICATIONS` | New notification created |
| `notificationsOverview` | `NotificationOverview!` | `READ_ANY` on `NOTIFICATIONS` | Overview counts change |
| `ownerSubscription` | `Owner!` | `READ_ANY` on `OWNER` | Owner info change |
| `serversSubscription` | `Server!` | `READ_ANY` on `SERVERS` | Server state change |
| `parityHistorySubscription` | `ParityCheck!` | `READ_ANY` on `ARRAY` | Parity check updates |
| `arraySubscription` | `UnraidArray!` | `READ_ANY` on `ARRAY` | Array state changes |
| `logFile(path: String!)` | `LogFileContent!` | `READ_ANY` on `LOGS` | Live log file tail |
| `systemMetricsCpu` | `CpuUtilization!` | `READ_ANY` on `INFO` | CPU utilization stream |
| `systemMetricsCpuTelemetry` | `CpuPackages!` | `READ_ANY` on `INFO` | CPU power/temp stream |
| `systemMetricsMemory` | `MemoryUtilization!` | `READ_ANY` on `INFO` | Memory utilization stream |
| `upsUpdates` | `UPSDevice!` | — | UPS state changes |
---
## Enums
### ArrayDiskFsColor
`GREEN_ON` · `GREEN_BLINK` · `BLUE_ON` · `BLUE_BLINK` · `YELLOW_ON` · `YELLOW_BLINK` · `RED_ON` · `RED_OFF` · `GREY_OFF`
### ArrayDiskStatus
`DISK_NP` · `DISK_OK` · `DISK_NP_MISSING` · `DISK_INVALID` · `DISK_WRONG` · `DISK_DSBL` · `DISK_NP_DSBL` · `DISK_DSBL_NEW` · `DISK_NEW`
### ArrayDiskType
`DATA` · `PARITY` · `FLASH` · `CACHE`
### ArrayState
`STARTED` · `STOPPED` · `NEW_ARRAY` · `RECON_DISK` · `DISABLE_DISK` · `SWAP_DSBL` · `INVALID_EXPANSION` · `PARITY_NOT_BIGGEST` · `TOO_MANY_MISSING_DISKS` · `NEW_DISK_TOO_SMALL` · `NO_DATA_DISKS`
### ArrayStateInputState
`START` · `STOP`
### AuthAction
`CREATE_ANY` · `CREATE_OWN` · `READ_ANY` · `READ_OWN` · `UPDATE_ANY` · `UPDATE_OWN` · `DELETE_ANY` · `DELETE_OWN`
### AuthorizationOperator
`EQUALS` · `CONTAINS` · `ENDS_WITH` · `STARTS_WITH`
### AuthorizationRuleMode
`OR` · `AND`
### ConfigErrorState
`UNKNOWN_ERROR` · `INELIGIBLE` · `INVALID` · `NO_KEY_SERVER` · `WITHDRAWN`
### ContainerPortType
`TCP` · `UDP`
### ContainerState
`RUNNING` · `EXITED`
### DiskFsType
`XFS` · `BTRFS` · `VFAT` · `ZFS` · `EXT4` · `NTFS`
### DiskInterfaceType
`SAS` · `SATA` · `USB` · `PCIE` · `UNKNOWN`
### DiskSmartStatus
`OK` · `UNKNOWN`
### NotificationImportance
`ALERT` · `INFO` · `WARNING`
### NotificationType
`UNREAD` · `ARCHIVE`
### ParityCheckStatus
`NEVER_RUN` · `RUNNING` · `PAUSED` · `COMPLETED` · `CANCELLED` · `FAILED`
### RegistrationState
`TRIAL` · `BASIC` · `PLUS` · `PRO` · `STARTER` · `UNLEASHED` · `LIFETIME` · `EEXPIRED` · `EGUID` · `EGUID1` · `ETRIAL` · `ENOKEYFILE` · `ENOKEYFILE1` · `ENOKEYFILE2` · `ENOFLASH` · `ENOFLASH1` · `ENOFLASH2` · `ENOFLASH3` · `ENOFLASH4` · `ENOFLASH5` · `ENOFLASH6` · `ENOFLASH7` · `EBLACKLISTED` · `EBLACKLISTED1` · `EBLACKLISTED2` · `ENOCONN`
### Resource
`ACTIVATION_CODE` · `API_KEY` · `ARRAY` · `CLOUD` · `CONFIG` · `CONNECT` · `CONNECT__REMOTE_ACCESS` · `CUSTOMIZATIONS` · `DASHBOARD` · `DISK` · `DISPLAY` · `DOCKER` · `FLASH` · `INFO` · `LOGS` · `ME` · `NETWORK` · `NOTIFICATIONS` · `ONLINE` · `OS` · `OWNER` · `PERMISSION` · `REGISTRATION` · `SERVERS` · `SERVICES` · `SHARE` · `VARS` · `VMS` · `WELCOME`
### Role
- `ADMIN` — Full administrative access
- `CONNECT` — Internal role for Unraid Connect
- `GUEST` — Basic read access (user profile only)
- `VIEWER` — Read-only access to all resources
### ServerStatus
`ONLINE` · `OFFLINE` · `NEVER_CONNECTED`
### Temperature
`CELSIUS` · `FAHRENHEIT`
### ThemeName
`azure` · `black` · `gray` · `white`
### UPSCableType
`USB` · `SIMPLE` · `SMART` · `ETHER` · `CUSTOM`
### UPSKillPower
`YES` · `NO`
### UPSServiceState
`ENABLE` · `DISABLE`
### UPSType
`USB` · `APCSMART` · `NET` · `SNMP` · `DUMB` · `PCNET` · `MODBUS`
### UpdateStatus
`UP_TO_DATE` · `UPDATE_AVAILABLE` · `REBUILD_READY` · `UNKNOWN`
### VmState
`NOSTATE` · `RUNNING` · `IDLE` · `PAUSED` · `SHUTDOWN` · `SHUTOFF` · `CRASHED` · `PMSUSPENDED`
### registrationType
`BASIC` · `PLUS` · `PRO` · `STARTER` · `UNLEASHED` · `LIFETIME` · `INVALID` · `TRIAL`
---
## Input Types
### NotificationData
```graphql
input NotificationData {
title: String!
subject: String!
description: String!
importance: NotificationImportance!
link: String
}
```
### NotificationFilter
```graphql
input NotificationFilter {
importance: NotificationImportance # optional filter
type: NotificationType! # UNREAD or ARCHIVE
offset: Int!
limit: Int!
}
```
### ArrayStateInput
```graphql
input ArrayStateInput {
desiredState: ArrayStateInputState! # START or STOP
}
```
### ArrayDiskInput
```graphql
input ArrayDiskInput {
id: PrefixedID!
slot: Int # optional slot number
}
```
### CreateApiKeyInput
```graphql
input CreateApiKeyInput {
name: String!
description: String
roles: [Role!]
permissions: [AddPermissionInput!]
overwrite: Boolean # replace existing key with same name
}
```
### UpdateApiKeyInput
```graphql
input UpdateApiKeyInput {
id: PrefixedID!
name: String
description: String
roles: [Role!]
permissions: [AddPermissionInput!]
}
```
### DeleteApiKeyInput
```graphql
input DeleteApiKeyInput {
ids: [PrefixedID!]!
}
```
### AddPermissionInput
```graphql
input AddPermissionInput {
resource: Resource!
actions: [AuthAction!]!
}
```
### AddRoleForApiKeyInput / RemoveRoleFromApiKeyInput
```graphql
input AddRoleForApiKeyInput {
apiKeyId: PrefixedID!
role: Role!
}
input RemoveRoleFromApiKeyInput {
apiKeyId: PrefixedID!
role: Role!
}
```
### CreateRCloneRemoteInput
```graphql
input CreateRCloneRemoteInput {
name: String!
type: String! # e.g. "drive", "s3", "sftp"
parameters: JSON! # provider-specific config
}
```
### DeleteRCloneRemoteInput
```graphql
input DeleteRCloneRemoteInput {
name: String!
}
```
### RCloneConfigFormInput
```graphql
input RCloneConfigFormInput {
providerType: String
showAdvanced: Boolean = false
parameters: JSON
}
```
### InitiateFlashBackupInput
```graphql
input InitiateFlashBackupInput {
remoteName: String! # configured remote name
sourcePath: String! # e.g. "/boot"
destinationPath: String! # remote destination path
options: JSON # e.g. {"--dry-run": true}
}
```
### UPSConfigInput
```graphql
input UPSConfigInput {
service: UPSServiceState # ENABLE or DISABLE
upsCable: UPSCableType # USB, SIMPLE, SMART, ETHER, CUSTOM
customUpsCable: String # only when upsCable=CUSTOM
upsType: UPSType # USB, APCSMART, NET, SNMP, DUMB, PCNET, MODBUS
device: String # /dev/ttyUSB0 or IP:port
overrideUpsCapacity: Int # watts
batteryLevel: Int # 0-100 percent shutdown threshold
minutes: Int # runtime minutes shutdown threshold
timeout: Int # seconds, 0=disable
killUps: UPSKillPower # YES or NO
}
```
### PluginManagementInput
```graphql
input PluginManagementInput {
names: [String!]!
bundled: Boolean! = false # treat as bundled plugins
restart: Boolean! = true # auto-restart API after operation
}
```
---
## Object Types (Full Field Reference)
### Key Types Quick Reference
| Type | Key Fields |
|------|-----------|
| `UnraidArray` | `state`, `capacity`, `boot`, `parities[]`, `parityCheckStatus`, `disks[]`, `caches[]` |
| `ArrayDisk` | `id`, `idx`, `name`, `device`, `size`, `status`, `temp`, `numReads/Writes/Errors`, `fsSize/Free/Used`, `type`, `color`, `isSpinning` |
| `Disk` | `id`, `device`, `type`, `name`, `vendor`, `size`, `serialNum`, `interfaceType`, `smartStatus`, `temperature`, `partitions[]`, `isSpinning` |
| `DockerContainer` | `id`, `names[]`, `image`, `state`, `status`, `ports[]`, `autoStart`, `labels`, `mounts[]` |
| `DockerNetwork` | `id`, `name`, `driver`, `scope`, `internal`, `attachable`, `containers`, `ipam` |
| `VmDomain` | `id`, `name`, `state`, `uuid` (deprecated) |
| `Notification` | `id`, `title`, `subject`, `description`, `importance`, `type`, `timestamp` |
| `Info` | `time`, `baseboard`, `cpu`, `devices`, `display`, `memory`, `os`, `system`, `versions` |
| `Metrics` | `cpu { percentTotal, cpus[] }`, `memory { total, used, free, percentTotal }` |
| `Share` | `id`, `name`, `free`, `used`, `size`, `include[]`, `exclude[]`, `cache`, `comment` |
| `ApiKey` | `id`, `key`, `name`, `description`, `roles[]`, `permissions[]`, `createdAt` |
| `UserAccount` | `id`, `name`, `description`, `roles[]`, `permissions[]` |
| `Server` | `id`, `name`, `status`, `guid`, `wanip`, `lanip`, `localurl`, `remoteurl`, `owner` |
| `Service` | `id`, `name`, `online`, `uptime`, `version` |
| `Owner` | `username`, `url`, `avatar` |
| `Registration` | `type`, `state`, `keyFile`, `expiration`, `updateExpiration` |
| `Vars` | 143 fields — hostname, timezone, array state, share config, registration, tuning params |
| `UPSDevice` | `id`, `name`, `model`, `status`, `battery { chargeLevel, estimatedRuntime, health }`, `power { inputVoltage, outputVoltage, loadPercentage }` |
| `UPSConfiguration` | `service`, `upsCable`, `upsType`, `device`, `batteryLevel`, `minutes`, `timeout`, `killUps`, + 4 more |
| `RCloneRemote` | `name`, `type`, `parameters`, `config` |
| `Settings` | `unified { dataSchema, uiSchema, values }`, `sso { oidcProviders[] }`, `api { version, extraOrigins }` |
| `Flash` | `guid`, `vendor`, `product` |
| `ParityCheck` | `date`, `duration`, `speed`, `status`, `errors`, `progress`, `correcting`, `paused`, `running` |
| `Plugin` | `name`, `version`, `hasApiModule`, `hasCliModule` |
---
## Schema Statistics
| Category | Count |
|----------|-------|
| Query fields | 46 |
| Mutation fields | 22 |
| Subscription fields | 11 |
| Object types | 94 |
| Input types | 16 |
| Enum types | 30 |
| Scalar types | 10 |
| Union types | 1 |
| Interface types | 2 |
| **Total types** | **156** |

View file

@ -116,9 +116,11 @@ unraid-mcp/
| +-- DESTRUCTIVE_ACTIONS.md # Destructive action reference
| +-- MARKETPLACE.md # Marketplace guide
| +-- PUBLISHING.md # Publishing guide
| +-- UNRAID_API_*.md # Unraid API documentation
| +-- unraid-schema.graphql # Full GraphQL schema
| +-- unraid-api-introspection.json # Schema introspection data
| +-- UNRAID-API-SUMMARY.md # Condensed Unraid API overview
| +-- UNRAID-API-CHANGES.md # Schema diff vs previous snapshot
| +-- UNRAID-API-COMPLETE-REFERENCE.md # Full Unraid API reference
| +-- UNRAID-SCHEMA.graphql # Full GraphQL schema
| +-- UNRAID-API-INTROSPECTION.json # Schema introspection data
|
+-- .github/
| +-- workflows/

View file

@ -72,13 +72,18 @@ bash scripts/validate-marketplace.sh
### generate_unraid_api_reference.py
Generates API reference documentation from GraphQL schema introspection:
Generates the canonical Unraid API docs from GraphQL schema introspection:
```bash
python scripts/generate_unraid_api_reference.py
```
Produces the docs in `docs/UNRAID_API_COMPLETE_REFERENCE.md`.
Produces:
- `docs/unraid/UNRAID-API-SUMMARY.md`
- `docs/unraid/UNRAID-API-COMPLETE-REFERENCE.md`
- `docs/unraid/UNRAID-API-INTROSPECTION.json`
- `docs/unraid/UNRAID-SCHEMA.graphql`
- `docs/unraid/UNRAID-API-CHANGES.md`
## Hook scripts (`hooks/scripts/`)

File diff suppressed because one or more lines are too long

View file

@ -1,723 +0,0 @@
# Unraid API v4.29.2 — Introspection Summary
> Auto-generated from live API introspection on 2026-03-15
> Source: tootie (10.1.0.2:31337)
## Table of Contents
- [Query Fields](#query-fields)
- [Mutation Fields](#mutation-fields)
- [Subscription Fields](#subscription-fields)
- [Enum Types](#enum-types)
- [Input Types](#input-types)
- [Object Types](#object-types)
## Query Fields
**46 fields**
| Field | Return Type | Arguments |
|-------|-------------|-----------|
| `apiKey` | `ApiKey` | `id: PrefixedID!` |
| `apiKeyPossiblePermissions` | `[Permission!]!` | — |
| `apiKeyPossibleRoles` | `[Role!]!` | — |
| `apiKeys` | `[ApiKey!]!` | — |
| `array` | `UnraidArray!` | — |
| `config` | `Config!` | — |
| `customization` | `Customization` | — |
| `disk` | `Disk!` | `id: PrefixedID!` |
| `disks` | `[Disk!]!` | — |
| `docker` | `Docker!` | — |
| `flash` | `Flash!` | — |
| `getApiKeyCreationFormSchema` | `ApiKeyFormSettings!` | — |
| `getAvailableAuthActions` | `[AuthAction!]!` | — |
| `getPermissionsForRoles` | `[Permission!]!` | `roles: [Role!]!` |
| `info` | `Info!` | — |
| `isInitialSetup` | `Boolean!` | — |
| `isSSOEnabled` | `Boolean!` | — |
| `logFile` | `LogFileContent!` | `path: String!`, `lines: Int`, `startLine: Int` |
| `logFiles` | `[LogFile!]!` | — |
| `me` | `UserAccount!` | — |
| `metrics` | `Metrics!` | — |
| `notifications` | `Notifications!` | — |
| `oidcConfiguration` | `OidcConfiguration!` | — |
| `oidcProvider` | `OidcProvider` | `id: PrefixedID!` |
| `oidcProviders` | `[OidcProvider!]!` | — |
| `online` | `Boolean!` | — |
| `owner` | `Owner!` | — |
| `parityHistory` | `[ParityCheck!]!` | — |
| `plugins` | `[Plugin!]!` | — |
| `previewEffectivePermissions` | `[Permission!]!` | `roles: [Role!]`, `permissions: [AddPermissionInput!]` |
| `publicOidcProviders` | `[PublicOidcProvider!]!` | — |
| `publicPartnerInfo` | `PublicPartnerInfo` | — |
| `publicTheme` | `Theme!` | — |
| `rclone` | `RCloneBackupSettings!` | — |
| `registration` | `Registration` | — |
| `server` | `Server` | — |
| `servers` | `[Server!]!` | — |
| `services` | `[Service!]!` | — |
| `settings` | `Settings!` | — |
| `shares` | `[Share!]!` | — |
| `upsConfiguration` | `UPSConfiguration!` | — |
| `upsDeviceById` | `UPSDevice` | `id: String!` |
| `upsDevices` | `[UPSDevice!]!` | — |
| `validateOidcSession` | `OidcSessionValidation!` | `token: String!` |
| `vars` | `Vars!` | — |
| `vms` | `Vms!` | — |
## Mutation Fields
**22 fields**
| Field | Return Type | Arguments |
|-------|-------------|-----------|
| `addPlugin` | `Boolean!` | `input: PluginManagementInput!` |
| `apiKey` | `ApiKeyMutations!` | — |
| `archiveAll` | `NotificationOverview!` | `importance: NotificationImportance` |
| `archiveNotification` | `Notification!` | `id: PrefixedID!` |
| `archiveNotifications` | `NotificationOverview!` | `ids: [PrefixedID!]!` |
| `array` | `ArrayMutations!` | — |
| `configureUps` | `Boolean!` | `config: UPSConfigInput!` |
| `createNotification` | `Notification!` | `input: NotificationData!` |
| `customization` | `CustomizationMutations!` | — |
| `deleteArchivedNotifications` | `NotificationOverview!` | — |
| `deleteNotification` | `NotificationOverview!` | `id: PrefixedID!`, `type: NotificationType!` |
| `docker` | `DockerMutations!` | — |
| `initiateFlashBackup` | `FlashBackupStatus!` | `input: InitiateFlashBackupInput!` |
| `parityCheck` | `ParityCheckMutations!` | — |
| `rclone` | `RCloneMutations!` | — |
| `recalculateOverview` | `NotificationOverview!` | — |
| `removePlugin` | `Boolean!` | `input: PluginManagementInput!` |
| `unarchiveAll` | `NotificationOverview!` | `importance: NotificationImportance` |
| `unarchiveNotifications` | `NotificationOverview!` | `ids: [PrefixedID!]!` |
| `unreadNotification` | `Notification!` | `id: PrefixedID!` |
| `updateSettings` | `UpdateSettingsResponse!` | `input: JSON!` |
| `vm` | `VmMutations!` | — |
## Subscription Fields
**11 fields**
| Field | Return Type | Arguments |
|-------|-------------|-----------|
| `arraySubscription` | `UnraidArray!` | — |
| `logFile` | `LogFileContent!` | `path: String!` |
| `notificationAdded` | `Notification!` | — |
| `notificationsOverview` | `NotificationOverview!` | — |
| `ownerSubscription` | `Owner!` | — |
| `parityHistorySubscription` | `ParityCheck!` | — |
| `serversSubscription` | `Server!` | — |
| `systemMetricsCpu` | `CpuUtilization!` | — |
| `systemMetricsCpuTelemetry` | `CpuPackages!` | — |
| `systemMetricsMemory` | `MemoryUtilization!` | — |
| `upsUpdates` | `UPSDevice!` | — |
## Enum Types
**30 enums**
### `ArrayDiskFsColor`
```
GREEN_ON
GREEN_BLINK
BLUE_ON
BLUE_BLINK
YELLOW_ON
YELLOW_BLINK
RED_ON
RED_OFF
GREY_OFF
```
### `ArrayDiskStatus`
```
DISK_NP
DISK_OK
DISK_NP_MISSING
DISK_INVALID
DISK_WRONG
DISK_DSBL
DISK_NP_DSBL
DISK_DSBL_NEW
DISK_NEW
```
### `ArrayDiskType`
```
DATA
PARITY
FLASH
CACHE
```
### `ArrayState`
```
STARTED
STOPPED
NEW_ARRAY
RECON_DISK
DISABLE_DISK
SWAP_DSBL
INVALID_EXPANSION
PARITY_NOT_BIGGEST
TOO_MANY_MISSING_DISKS
NEW_DISK_TOO_SMALL
NO_DATA_DISKS
```
### `ArrayStateInputState`
```
START
STOP
```
### `AuthAction`
> Authentication actions with possession (e.g., create:any, read:own)
```
CREATE_ANY
CREATE_OWN
READ_ANY
READ_OWN
UPDATE_ANY
UPDATE_OWN
DELETE_ANY
DELETE_OWN
```
### `AuthorizationOperator`
> Operators for authorization rule matching
```
EQUALS
CONTAINS
ENDS_WITH
STARTS_WITH
```
### `AuthorizationRuleMode`
> Mode for evaluating authorization rules - OR (any rule passes) or AND (all rules must pass)
```
OR
AND
```
### `ConfigErrorState`
> Possible error states for configuration
```
UNKNOWN_ERROR
INELIGIBLE
INVALID
NO_KEY_SERVER
WITHDRAWN
```
### `ContainerPortType`
```
TCP
UDP
```
### `ContainerState`
```
RUNNING
EXITED
```
### `DiskFsType`
> The type of filesystem on the disk partition
```
XFS
BTRFS
VFAT
ZFS
EXT4
NTFS
```
### `DiskInterfaceType`
> The type of interface the disk uses to connect to the system
```
SAS
SATA
USB
PCIE
UNKNOWN
```
### `DiskSmartStatus`
> The SMART (Self-Monitoring, Analysis and Reporting Technology) status of the disk
```
OK
UNKNOWN
```
### `NotificationImportance`
```
ALERT
INFO
WARNING
```
### `NotificationType`
```
UNREAD
ARCHIVE
```
### `ParityCheckStatus`
```
NEVER_RUN
RUNNING
PAUSED
COMPLETED
CANCELLED
FAILED
```
### `RegistrationState`
```
TRIAL
BASIC
PLUS
PRO
STARTER
UNLEASHED
LIFETIME
EEXPIRED
EGUID
EGUID1
ETRIAL
ENOKEYFILE
ENOKEYFILE1
ENOKEYFILE2
ENOFLASH
ENOFLASH1
ENOFLASH2
ENOFLASH3
ENOFLASH4
ENOFLASH5
ENOFLASH6
ENOFLASH7
EBLACKLISTED
EBLACKLISTED1
EBLACKLISTED2
ENOCONN
```
### `Resource`
> Available resources for permissions
```
ACTIVATION_CODE
API_KEY
ARRAY
CLOUD
CONFIG
CONNECT
CONNECT__REMOTE_ACCESS
CUSTOMIZATIONS
DASHBOARD
DISK
DISPLAY
DOCKER
FLASH
INFO
LOGS
ME
NETWORK
NOTIFICATIONS
ONLINE
OS
OWNER
PERMISSION
REGISTRATION
SERVERS
SERVICES
SHARE
VARS
VMS
WELCOME
```
### `Role`
> Available roles for API keys and users
```
ADMIN
CONNECT
GUEST
VIEWER
```
### `ServerStatus`
```
ONLINE
OFFLINE
NEVER_CONNECTED
```
### `Temperature`
> Temperature unit
```
CELSIUS
FAHRENHEIT
```
### `ThemeName`
> The theme name
```
azure
black
gray
white
```
### `UPSCableType`
> UPS cable connection types
```
USB
SIMPLE
SMART
ETHER
CUSTOM
```
### `UPSKillPower`
> Kill UPS power after shutdown option
```
YES
NO
```
### `UPSServiceState`
> Service state for UPS daemon
```
ENABLE
DISABLE
```
### `UPSType`
> UPS communication protocols
```
USB
APCSMART
NET
SNMP
DUMB
PCNET
MODBUS
```
### `UpdateStatus`
> Update status of a container.
```
UP_TO_DATE
UPDATE_AVAILABLE
REBUILD_READY
UNKNOWN
```
### `VmState`
> The state of a virtual machine
```
NOSTATE
RUNNING
IDLE
PAUSED
SHUTDOWN
SHUTOFF
CRASHED
PMSUSPENDED
```
### `registrationType`
```
BASIC
PLUS
PRO
STARTER
UNLEASHED
LIFETIME
INVALID
TRIAL
```
## Input Types
**16 input types**
### `AddPermissionInput`
| Field | Type | Default |
|-------|------|---------|
| `resource` | `Resource!` | — |
| `actions` | `[AuthAction!]!` | — |
### `AddRoleForApiKeyInput`
| Field | Type | Default |
|-------|------|---------|
| `apiKeyId` | `PrefixedID!` | — |
| `role` | `Role!` | — |
### `ArrayDiskInput`
| Field | Type | Default |
|-------|------|---------|
| `id` | `PrefixedID!` | — |
| `slot` | `Int` | — |
### `ArrayStateInput`
| Field | Type | Default |
|-------|------|---------|
| `desiredState` | `ArrayStateInputState!` | — |
### `CreateApiKeyInput`
| Field | Type | Default |
|-------|------|---------|
| `name` | `String!` | — |
| `description` | `String` | — |
| `roles` | `[Role!]` | — |
| `permissions` | `[AddPermissionInput!]` | — |
| `overwrite` | `Boolean` | — |
### `CreateRCloneRemoteInput`
| Field | Type | Default |
|-------|------|---------|
| `name` | `String!` | — |
| `type` | `String!` | — |
| `parameters` | `JSON!` | — |
### `DeleteApiKeyInput`
| Field | Type | Default |
|-------|------|---------|
| `ids` | `[PrefixedID!]!` | — |
### `DeleteRCloneRemoteInput`
| Field | Type | Default |
|-------|------|---------|
| `name` | `String!` | — |
### `InitiateFlashBackupInput`
| Field | Type | Default |
|-------|------|---------|
| `remoteName` | `String!` | — |
| `sourcePath` | `String!` | — |
| `destinationPath` | `String!` | — |
| `options` | `JSON` | — |
### `NotificationData`
| Field | Type | Default |
|-------|------|---------|
| `title` | `String!` | — |
| `subject` | `String!` | — |
| `description` | `String!` | — |
| `importance` | `NotificationImportance!` | — |
| `link` | `String` | — |
### `NotificationFilter`
| Field | Type | Default |
|-------|------|---------|
| `importance` | `NotificationImportance` | — |
| `type` | `NotificationType!` | — |
| `offset` | `Int!` | — |
| `limit` | `Int!` | — |
### `PluginManagementInput`
| Field | Type | Default |
|-------|------|---------|
| `names` | `[String!]!` | — |
| `bundled` | `Boolean!` | false |
| `restart` | `Boolean!` | true |
### `RCloneConfigFormInput`
| Field | Type | Default |
|-------|------|---------|
| `providerType` | `String` | — |
| `showAdvanced` | `Boolean` | false |
| `parameters` | `JSON` | — |
### `RemoveRoleFromApiKeyInput`
| Field | Type | Default |
|-------|------|---------|
| `apiKeyId` | `PrefixedID!` | — |
| `role` | `Role!` | — |
### `UPSConfigInput`
| Field | Type | Default |
|-------|------|---------|
| `service` | `UPSServiceState` | — |
| `upsCable` | `UPSCableType` | — |
| `customUpsCable` | `String` | — |
| `upsType` | `UPSType` | — |
| `device` | `String` | — |
| `overrideUpsCapacity` | `Int` | — |
| `batteryLevel` | `Int` | — |
| `minutes` | `Int` | — |
| `timeout` | `Int` | — |
| `killUps` | `UPSKillPower` | — |
### `UpdateApiKeyInput`
| Field | Type | Default |
|-------|------|---------|
| `id` | `PrefixedID!` | — |
| `name` | `String` | — |
| `description` | `String` | — |
| `roles` | `[Role!]` | — |
| `permissions` | `[AddPermissionInput!]` | — |
## Object Types
**94 object types**
| Type | Fields | Description |
|------|--------|-------------|
| `ActivationCode` | 11 | — |
| `ApiConfig` | 5 | — |
| `ApiKey` | 7 | — |
| `ApiKeyFormSettings` | 4 | — |
| `ApiKeyMutations` | 5 | API Key related mutations |
| `ArrayCapacity` | 2 | — |
| `ArrayDisk` | 24 | — |
| `ArrayMutations` | 6 | — |
| `Capacity` | 3 | — |
| `Config` | 3 | — |
| `ContainerHostConfig` | 1 | — |
| `ContainerPort` | 4 | — |
| `CoreVersions` | 3 | — |
| `CpuLoad` | 8 | CPU load for a single core |
| `CpuPackages` | 4 | — |
| `CpuUtilization` | 3 | — |
| `Customization` | 3 | — |
| `CustomizationMutations` | 1 | Customization related mutations |
| `Disk` | 20 | — |
| `DiskPartition` | 3 | — |
| `Docker` | 3 | — |
| `DockerContainer` | 15 | — |
| `DockerMutations` | 2 | — |
| `DockerNetwork` | 15 | — |
| `ExplicitStatusItem` | 2 | — |
| `Flash` | 4 | — |
| `FlashBackupStatus` | 2 | — |
| `Info` | 11 | — |
| `InfoBaseboard` | 8 | — |
| `InfoCpu` | 20 | — |
| `InfoDevices` | 5 | — |
| `InfoDisplay` | 16 | — |
| `InfoDisplayCase` | 5 | — |
| `InfoGpu` | 7 | — |
| `InfoMemory` | 2 | — |
| `InfoNetwork` | 8 | — |
| `InfoOs` | 15 | — |
| `InfoPci` | 9 | — |
| `InfoSystem` | 8 | — |
| `InfoUsb` | 4 | — |
| `InfoVersions` | 3 | — |
| `KeyFile` | 2 | — |
| `LogFile` | 4 | — |
| `LogFileContent` | 4 | — |
| `MemoryLayout` | 12 | — |
| `MemoryUtilization` | 12 | — |
| `Metrics` | 3 | System metrics including CPU and memory utilization |
| `Notification` | 9 | — |
| `NotificationCounts` | 4 | — |
| `NotificationOverview` | 2 | — |
| `Notifications` | 3 | — |
| `OidcAuthorizationRule` | 3 | — |
| `OidcConfiguration` | 2 | — |
| `OidcProvider` | 15 | — |
| `OidcSessionValidation` | 2 | — |
| `OrganizerContainerResource` | 4 | — |
| `OrganizerResource` | 4 | — |
| `Owner` | 3 | — |
| `PackageVersions` | 8 | — |
| `ParityCheck` | 9 | — |
| `ParityCheckMutations` | 4 | Parity check related mutations, WIP, response types and functionaliy will change |
| `Permission` | 2 | — |
| `Plugin` | 4 | — |
| `ProfileModel` | 4 | — |
| `PublicOidcProvider` | 6 | — |
| `PublicPartnerInfo` | 4 | — |
| `RCloneBackupConfigForm` | 3 | — |
| `RCloneBackupSettings` | 3 | — |
| `RCloneDrive` | 2 | — |
| `RCloneMutations` | 2 | RClone related mutations |
| `RCloneRemote` | 4 | — |
| `Registration` | 6 | — |
| `ResolvedOrganizerFolder` | 4 | — |
| `ResolvedOrganizerV1` | 2 | — |
| `ResolvedOrganizerView` | 4 | — |
| `Server` | 10 | — |
| `Service` | 5 | — |
| `Settings` | 4 | — |
| `Share` | 16 | — |
| `SsoSettings` | 2 | — |
| `Theme` | 7 | — |
| `UPSBattery` | 3 | — |
| `UPSConfiguration` | 14 | — |
| `UPSDevice` | 6 | — |
| `UPSPower` | 3 | — |
| `UnifiedSettings` | 4 | — |
| `UnraidArray` | 8 | — |
| `UpdateSettingsResponse` | 3 | — |
| `Uptime` | 1 | — |
| `UserAccount` | 5 | — |
| `Vars` | 143 | — |
| `VmDomain` | 4 | — |
| `VmMutations` | 7 | — |
| `Vms` | 3 | — |
## Schema Statistics
| Category | Count |
|----------|-------|
| Query fields | 46 |
| Mutation fields | 22 |
| Subscription fields | 11 |
| Object types | 94 |
| Input types | 16 |
| Enum types | 30 |
| Scalar types | 10 |
| Union types | 1 |
| Interface types | 2 |
| **Total types** | **156** |

View file

@ -1,9 +0,0 @@
server {
listen 443 ssl;
server_name unraid.*;
include /config/nginx/ssl.conf;
location / {
include /config/nginx/proxy.conf;
proxy_pass http://unraid-mcp:6970;
}
}

View file

@ -0,0 +1,399 @@
# Unraid API Schema Changes
> Generated on 2026-04-05T12:02:01+00:00
> Source: https://10-1-0-2.95d289568cc4a4bdc8e0d50284d6f455ec0eae5f.myunraid.net:31337/graphql
## Query fields
- Added: 13
- `assignableDisks`
- `cloud`
- `connect`
- `display`
- `installedUnraidPlugins`
- `internalBootContext`
- `isFreshInstall`
- `network`
- `pluginInstallOperation`
- `pluginInstallOperations`
- `remoteAccess`
- `systemTime`
- `timeZoneOptions`
- Removed: 2
- `isInitialSetup`
- `publicPartnerInfo`
## Mutation fields
- Added: 23
- `connectSignIn`
- `connectSignOut`
- `createDockerFolder`
- `createDockerFolderWithItems`
- `deleteDockerEntries`
- `enableDynamicRemoteAccess`
- `moveDockerEntriesToFolder`
- `moveDockerItemsToPosition`
- `notifyIfUnique`
- `onboarding`
- `refreshDockerDigests`
- `renameDockerFolder`
- `resetDockerTemplateMappings`
- `setDockerFolderChildren`
- `setupRemoteAccess`
- `syncDockerTemplatePaths`
- `unraidPlugins`
- `updateApiSettings`
- `updateDockerViewPreferences`
- `updateServerIdentity`
- `updateSshSettings`
- `updateSystemTime`
- `updateTemperatureConfig`
- Removed: 0
## Subscription fields
- Added: 5
- `displaySubscription`
- `dockerContainerStats`
- `notificationsWarningsAndAlerts`
- `pluginInstallUpdates`
- `systemMetricsTemperature`
- Removed: 0
## Type Changes
### ENUM
- Added: 10
- `DynamicRemoteAccessType`
- `MinigraphStatus`
- `OnboardingStatus`
- `PluginInstallStatus`
- `SensorType`
- `TemperatureStatus`
- `TemperatureUnit`
- `URL_TYPE`
- `WAN_ACCESS_TYPE`
- `WAN_FORWARD_TYPE`
- Removed: 0
### INPUT_OBJECT
- Added: 27
- `AccessUrlInput`
- `AccessUrlObjectInput`
- `ActivationCodeOverrideInput`
- `BrandingConfigInput`
- `ConnectSettingsInput`
- `ConnectSignInInput`
- `ConnectUserInfoInput`
- `CreateInternalBootPoolInput`
- `DockerAutostartEntryInput`
- `EnableDynamicRemoteAccessInput`
- `InstallPluginInput`
- `IpmiConfigInput`
- `LmSensorsConfigInput`
- `OnboardingOverrideCompletionInput`
- `OnboardingOverrideInput`
- `PartnerConfigInput`
- `PartnerInfoOverrideInput`
- `PartnerLinkInput`
- `SensorConfigInput`
- `SetupRemoteAccessInput`
- `SystemConfigInput`
- `TemperatureConfigInput`
- `TemperatureHistoryConfigInput`
- `TemperatureSensorsConfigInput`
- `TemperatureThresholdsConfigInput`
- `UpdateSshInput`
- `UpdateSystemTimeInput`
- Removed: 0
### OBJECT
- Added: 50
- `AccessUrl`
- `AccessUrlObject`
- `ApiKeyResponse`
- `BrandingConfig`
- `Cloud`
- `CloudResponse`
- `Connect`
- `ConnectSettings`
- `ConnectSettingsValues`
- `DockerContainerLogLine`
- `DockerContainerLogs`
- `DockerContainerPortConflict`
- `DockerContainerStats`
- `DockerLanPortConflict`
- `DockerPortConflictContainer`
- `DockerPortConflicts`
- `DockerTemplateSyncResult`
- `DynamicRemoteAccessStatus`
- `FlatOrganizerEntry`
- `InfoNetworkInterface`
- `IpmiConfig`
- `Language`
- `LmSensorsConfig`
- `MinigraphqlResponse`
- `Network`
- `Onboarding`
- `OnboardingInternalBootContext`
- `OnboardingInternalBootResult`
- `OnboardingMutations`
- `OnboardingState`
- `PartnerConfig`
- `PartnerLink`
- `PluginInstallEvent`
- `PluginInstallOperation`
- `RelayResponse`
- `RemoteAccess`
- `SensorConfig`
- `SystemConfig`
- `SystemTime`
- `TailscaleExitNodeStatus`
- `TailscaleStatus`
- `TemperatureHistoryConfig`
- `TemperatureMetrics`
- `TemperatureReading`
- `TemperatureSensor`
- `TemperatureSensorsConfig`
- `TemperatureSummary`
- `TemperatureThresholdsConfig`
- `TimeZoneOption`
- `UnraidPluginsMutations`
- Removed: 4
- `OrganizerContainerResource`
- `OrganizerResource`
- `PublicPartnerInfo`
- `ResolvedOrganizerFolder`
### SCALAR
- Added: 1
- `URL`
- Removed: 0
### UNION
- Added: 0
- Removed: 1
- `ResolvedOrganizerEntry`
## Type Signature Changes
### `ActivationCode` (OBJECT)
- Added members: 3
- `branding(): BrandingConfig`
- `partner(): PartnerConfig`
- `system(): SystemConfig`
- Removed members: 10
- `background(): String`
- `comment(): String`
- `header(): String`
- `headermetacolor(): String`
- `partnerName(): String`
- `partnerUrl(): String`
- `serverName(): String`
- `showBannerGradient(): Boolean`
- `sysModel(): String`
- `theme(): String`
### `ArrayDiskType` (ENUM)
- Added members: 1
- `BOOT`
- Removed members: 0
### `ArrayStateInput` (INPUT_OBJECT)
- Added members: 2
- `decryptionKeyfile: String`
- `decryptionPassword: String`
- Removed members: 0
### `ContainerState` (ENUM)
- Added members: 1
- `PAUSED`
- Removed members: 0
### `Customization` (OBJECT)
- Added members: 2
- `availableLanguages(): [Language!]`
- `onboarding(): Onboarding!`
- Removed members: 2
- `partnerInfo(): PublicPartnerInfo`
- `theme(): Theme!`
### `CustomizationMutations` (OBJECT)
- Added members: 1
- `setLocale(locale: String!): String!`
- Removed members: 0
### `Docker` (OBJECT)
- Added members: 7
- `container(id: PrefixedID!): DockerContainer`
- `containerUpdateStatuses(): [ExplicitStatusItem!]!`
- `containers(): [DockerContainer!]!`
- `logs(id: PrefixedID!, since: DateTime, tail: Int): DockerContainerLogs!`
- `networks(): [DockerNetwork!]!`
- `organizer(): ResolvedOrganizerV1!`
- `portConflicts(): DockerPortConflicts!`
- Removed members: 2
- `containers(skipCache: Boolean! = false): [DockerContainer!]!`
- `networks(skipCache: Boolean! = false): [DockerNetwork!]!`
### `DockerContainer` (OBJECT)
- Added members: 18
- `autoStartOrder(): Int`
- `autoStartWait(): Int`
- `iconUrl(): String`
- `isOrphaned(): Boolean!`
- `isRebuildReady(): Boolean`
- `isUpdateAvailable(): Boolean`
- `lanIpPorts(): [String!]`
- `projectUrl(): String`
- `registryUrl(): String`
- `shell(): String`
- `sizeLog(): BigInt`
- `sizeRw(): BigInt`
- `supportUrl(): String`
- `tailscaleEnabled(): Boolean!`
- `tailscaleStatus(forceRefresh: Boolean = false): TailscaleStatus`
- `templatePath(): String`
- `templatePorts(): [ContainerPort!]`
- `webUiUrl(): String`
- Removed members: 0
### `DockerMutations` (OBJECT)
- Added members: 7
- `pause(id: PrefixedID!): DockerContainer!`
- `removeContainer(id: PrefixedID!, withImage: Boolean): Boolean!`
- `unpause(id: PrefixedID!): DockerContainer!`
- `updateAllContainers(): [DockerContainer!]!`
- `updateAutostartConfiguration(entries: [DockerAutostartEntryInput!]!, persistUserPreferences: Boolean): Boolean!`
- `updateContainer(id: PrefixedID!): DockerContainer!`
- `updateContainers(ids: [PrefixedID!]!): [DockerContainer!]!`
- Removed members: 0
### `Info` (OBJECT)
- Added members: 2
- `networkInterfaces(): [InfoNetworkInterface!]!`
- `primaryNetwork(): InfoNetworkInterface`
- Removed members: 0
### `Metrics` (OBJECT)
- Added members: 1
- `temperature(): TemperatureMetrics`
- Removed members: 0
### `Mutation` (OBJECT)
- Added members: 23
- `connectSignIn(input: ConnectSignInInput!): Boolean!`
- `connectSignOut(): Boolean!`
- `createDockerFolder(childrenIds: [String!], name: String!, parentId: String): ResolvedOrganizerV1!`
- `createDockerFolderWithItems(name: String!, parentId: String, position: Float, sourceEntryIds: [String!]): ResolvedOrganizerV1!`
- `deleteDockerEntries(entryIds: [String!]!): ResolvedOrganizerV1!`
- `enableDynamicRemoteAccess(input: EnableDynamicRemoteAccessInput!): Boolean!`
- `moveDockerEntriesToFolder(destinationFolderId: String!, sourceEntryIds: [String!]!): ResolvedOrganizerV1!`
- `moveDockerItemsToPosition(destinationFolderId: String!, position: Float!, sourceEntryIds: [String!]!): ResolvedOrganizerV1!`
- `notifyIfUnique(input: NotificationData!): Notification`
- `onboarding(): OnboardingMutations!`
- `refreshDockerDigests(): Boolean!`
- `renameDockerFolder(folderId: String!, newName: String!): ResolvedOrganizerV1!`
- `resetDockerTemplateMappings(): Boolean!`
- `setDockerFolderChildren(childrenIds: [String!]!, folderId: String): ResolvedOrganizerV1!`
- `setupRemoteAccess(input: SetupRemoteAccessInput!): Boolean!`
- `syncDockerTemplatePaths(): DockerTemplateSyncResult!`
- `unraidPlugins(): UnraidPluginsMutations!`
- `updateApiSettings(input: ConnectSettingsInput!): ConnectSettingsValues!`
- `updateDockerViewPreferences(prefs: JSON!, viewId: String = "default"): ResolvedOrganizerV1!`
- `updateServerIdentity(comment: String, name: String!, sysModel: String): Server!`
- `updateSshSettings(input: UpdateSshInput!): Vars!`
- `updateSystemTime(input: UpdateSystemTimeInput!): SystemTime!`
- `updateTemperatureConfig(input: TemperatureConfigInput!): Boolean!`
- Removed members: 0
### `Notifications` (OBJECT)
- Added members: 1
- `warningsAndAlerts(): [Notification!]!`
- Removed members: 0
### `Query` (OBJECT)
- Added members: 13
- `assignableDisks(): [Disk!]!`
- `cloud(): Cloud!`
- `connect(): Connect!`
- `display(): InfoDisplay!`
- `installedUnraidPlugins(): [String!]!`
- `internalBootContext(): OnboardingInternalBootContext!`
- `isFreshInstall(): Boolean!`
- `network(): Network!`
- `pluginInstallOperation(operationId: ID!): PluginInstallOperation`
- `pluginInstallOperations(): [PluginInstallOperation!]!`
- `remoteAccess(): RemoteAccess!`
- `systemTime(): SystemTime!`
- `timeZoneOptions(): [TimeZoneOption!]!`
- Removed members: 2
- `isInitialSetup(): Boolean!`
- `publicPartnerInfo(): PublicPartnerInfo`
### `ResolvedOrganizerView` (OBJECT)
- Added members: 2
- `flatEntries(): [FlatOrganizerEntry!]!`
- `rootId(): String!`
- Removed members: 1
- `root(): ResolvedOrganizerEntry!`
### `Server` (OBJECT)
- Added members: 1
- `comment(): String`
- Removed members: 0
### `Subscription` (OBJECT)
- Added members: 5
- `displaySubscription(): InfoDisplay!`
- `dockerContainerStats(): DockerContainerStats!`
- `notificationsWarningsAndAlerts(): [Notification!]!`
- `pluginInstallUpdates(operationId: ID!): PluginInstallEvent!`
- `systemMetricsTemperature(): TemperatureMetrics`
- Removed members: 0
### `UPSPower` (OBJECT)
- Added members: 2
- `currentPower(): Float`
- `nominalPower(): Int`
- Removed members: 0
### `UnraidArray` (OBJECT)
- Added members: 1
- `bootDevices(): [ArrayDisk!]!`
- Removed members: 0
### `Vars` (OBJECT)
- Added members: 5
- `bootEligible(): Boolean`
- `bootedFromFlashWithInternalBootSetup(): Boolean`
- `enableBootTransfer(): String`
- `reservedNames(): String`
- `tpmGuid(): String`
- Removed members: 0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,165 @@
# Unraid API Introspection Summary
> Auto-generated from live API introspection on 2026-04-05T12:02:01+00:00
> Source: https://10-1-0-2.95d289568cc4a4bdc8e0d50284d6f455ec0eae5f.myunraid.net:31337/graphql
## Table of Contents
- [Schema Summary](#schema-summary)
- [Query Fields](#query-fields)
- [Mutation Fields](#mutation-fields)
- [Subscription Fields](#subscription-fields)
- [Type Kinds](#type-kinds)
## Schema Summary
- Query root: `Query`
- Mutation root: `Mutation`
- Subscription root: `Subscription`
- Total types: **239**
- Total directives: **6**
## Query Fields
| Field | Return Type | Arguments |
|-------|-------------|-----------|
| `apiKey` | `ApiKey` | id: PrefixedID! |
| `apiKeyPossiblePermissions` | `[Permission!]!` | — |
| `apiKeyPossibleRoles` | `[Role!]!` | — |
| `apiKeys` | `[ApiKey!]!` | — |
| `array` | `UnraidArray!` | — |
| `assignableDisks` | `[Disk!]!` | — |
| `cloud` | `Cloud!` | — |
| `config` | `Config!` | — |
| `connect` | `Connect!` | — |
| `customization` | `Customization` | — |
| `disk` | `Disk!` | id: PrefixedID! |
| `disks` | `[Disk!]!` | — |
| `display` | `InfoDisplay!` | — |
| `docker` | `Docker!` | — |
| `flash` | `Flash!` | — |
| `getApiKeyCreationFormSchema` | `ApiKeyFormSettings!` | — |
| `getAvailableAuthActions` | `[AuthAction!]!` | — |
| `getPermissionsForRoles` | `[Permission!]!` | roles: [Role!]! |
| `info` | `Info!` | — |
| `installedUnraidPlugins` | `[String!]!` | — |
| `internalBootContext` | `OnboardingInternalBootContext!` | — |
| `isFreshInstall` | `Boolean!` | — |
| `isSSOEnabled` | `Boolean!` | — |
| `logFile` | `LogFileContent!` | lines: Int, path: String!, startLine: Int |
| `logFiles` | `[LogFile!]!` | — |
| `me` | `UserAccount!` | — |
| `metrics` | `Metrics!` | — |
| `network` | `Network!` | — |
| `notifications` | `Notifications!` | — |
| `oidcConfiguration` | `OidcConfiguration!` | — |
| `oidcProvider` | `OidcProvider` | id: PrefixedID! |
| `oidcProviders` | `[OidcProvider!]!` | — |
| `online` | `Boolean!` | — |
| `owner` | `Owner!` | — |
| `parityHistory` | `[ParityCheck!]!` | — |
| `pluginInstallOperation` | `PluginInstallOperation` | operationId: ID! |
| `pluginInstallOperations` | `[PluginInstallOperation!]!` | — |
| `plugins` | `[Plugin!]!` | — |
| `previewEffectivePermissions` | `[Permission!]!` | permissions: [AddPermissionInput!], roles: [Role!] |
| `publicOidcProviders` | `[PublicOidcProvider!]!` | — |
| `publicTheme` | `Theme!` | — |
| `rclone` | `RCloneBackupSettings!` | — |
| `registration` | `Registration` | — |
| `remoteAccess` | `RemoteAccess!` | — |
| `server` | `Server` | — |
| `servers` | `[Server!]!` | — |
| `services` | `[Service!]!` | — |
| `settings` | `Settings!` | — |
| `shares` | `[Share!]!` | — |
| `systemTime` | `SystemTime!` | — |
| `timeZoneOptions` | `[TimeZoneOption!]!` | — |
| `upsConfiguration` | `UPSConfiguration!` | — |
| `upsDeviceById` | `UPSDevice` | id: String! |
| `upsDevices` | `[UPSDevice!]!` | — |
| `validateOidcSession` | `OidcSessionValidation!` | token: String! |
| `vars` | `Vars!` | — |
| `vms` | `Vms!` | — |
## Mutation Fields
| Field | Return Type | Arguments |
|-------|-------------|-----------|
| `addPlugin` | `Boolean!` | input: PluginManagementInput! |
| `apiKey` | `ApiKeyMutations!` | — |
| `archiveAll` | `NotificationOverview!` | importance: NotificationImportance |
| `archiveNotification` | `Notification!` | id: PrefixedID! |
| `archiveNotifications` | `NotificationOverview!` | ids: [PrefixedID!]! |
| `array` | `ArrayMutations!` | — |
| `configureUps` | `Boolean!` | config: UPSConfigInput! |
| `connectSignIn` | `Boolean!` | input: ConnectSignInInput! |
| `connectSignOut` | `Boolean!` | — |
| `createDockerFolder` | `ResolvedOrganizerV1!` | childrenIds: [String!], name: String!, parentId: String |
| `createDockerFolderWithItems` | `ResolvedOrganizerV1!` | name: String!, parentId: String, position: Float, sourceEntryIds: [String!] |
| `createNotification` | `Notification!` | input: NotificationData! |
| `customization` | `CustomizationMutations!` | — |
| `deleteArchivedNotifications` | `NotificationOverview!` | — |
| `deleteDockerEntries` | `ResolvedOrganizerV1!` | entryIds: [String!]! |
| `deleteNotification` | `NotificationOverview!` | id: PrefixedID!, type: NotificationType! |
| `docker` | `DockerMutations!` | — |
| `enableDynamicRemoteAccess` | `Boolean!` | input: EnableDynamicRemoteAccessInput! |
| `initiateFlashBackup` | `FlashBackupStatus!` | input: InitiateFlashBackupInput! |
| `moveDockerEntriesToFolder` | `ResolvedOrganizerV1!` | destinationFolderId: String!, sourceEntryIds: [String!]! |
| `moveDockerItemsToPosition` | `ResolvedOrganizerV1!` | destinationFolderId: String!, position: Float!, sourceEntryIds: [String!]! |
| `notifyIfUnique` | `Notification` | input: NotificationData! |
| `onboarding` | `OnboardingMutations!` | — |
| `parityCheck` | `ParityCheckMutations!` | — |
| `rclone` | `RCloneMutations!` | — |
| `recalculateOverview` | `NotificationOverview!` | — |
| `refreshDockerDigests` | `Boolean!` | — |
| `removePlugin` | `Boolean!` | input: PluginManagementInput! |
| `renameDockerFolder` | `ResolvedOrganizerV1!` | folderId: String!, newName: String! |
| `resetDockerTemplateMappings` | `Boolean!` | — |
| `setDockerFolderChildren` | `ResolvedOrganizerV1!` | childrenIds: [String!]!, folderId: String |
| `setupRemoteAccess` | `Boolean!` | input: SetupRemoteAccessInput! |
| `syncDockerTemplatePaths` | `DockerTemplateSyncResult!` | — |
| `unarchiveAll` | `NotificationOverview!` | importance: NotificationImportance |
| `unarchiveNotifications` | `NotificationOverview!` | ids: [PrefixedID!]! |
| `unraidPlugins` | `UnraidPluginsMutations!` | — |
| `unreadNotification` | `Notification!` | id: PrefixedID! |
| `updateApiSettings` | `ConnectSettingsValues!` | input: ConnectSettingsInput! |
| `updateDockerViewPreferences` | `ResolvedOrganizerV1!` | prefs: JSON!, viewId: String (default: "default") |
| `updateServerIdentity` | `Server!` | comment: String, name: String!, sysModel: String |
| `updateSettings` | `UpdateSettingsResponse!` | input: JSON! |
| `updateSshSettings` | `Vars!` | input: UpdateSshInput! |
| `updateSystemTime` | `SystemTime!` | input: UpdateSystemTimeInput! |
| `updateTemperatureConfig` | `Boolean!` | input: TemperatureConfigInput! |
| `vm` | `VmMutations!` | — |
## Subscription Fields
| Field | Return Type | Arguments |
|-------|-------------|-----------|
| `arraySubscription` | `UnraidArray!` | — |
| `displaySubscription` | `InfoDisplay!` | — |
| `dockerContainerStats` | `DockerContainerStats!` | — |
| `logFile` | `LogFileContent!` | path: String! |
| `notificationAdded` | `Notification!` | — |
| `notificationsOverview` | `NotificationOverview!` | — |
| `notificationsWarningsAndAlerts` | `[Notification!]!` | — |
| `ownerSubscription` | `Owner!` | — |
| `parityHistorySubscription` | `ParityCheck!` | — |
| `pluginInstallUpdates` | `PluginInstallEvent!` | operationId: ID! |
| `serversSubscription` | `Server!` | — |
| `systemMetricsCpu` | `CpuUtilization!` | — |
| `systemMetricsCpuTelemetry` | `CpuPackages!` | — |
| `systemMetricsMemory` | `MemoryUtilization!` | — |
| `systemMetricsTemperature` | `TemperatureMetrics` | — |
| `upsUpdates` | `UPSDevice!` | — |
## Type Kinds
- `ENUM`: 40
- `INPUT_OBJECT`: 43
- `INTERFACE`: 2
- `OBJECT`: 143
- `SCALAR`: 11
## Notes
- This summary is intentionally condensed; the full schema reference lives in `UNRAID-API-COMPLETE-REFERENCE.md`.
- Raw schema exports live in `UNRAID-API-INTROSPECTION.json` and `UNRAID-SCHEMA.graphql`.

View file

@ -346,11 +346,6 @@ type Disk implements Node {
"""The serial number of the disk"""
serialNum: String!
"""
Device identifier from emhttp devs.ini used by disk assignment commands
"""
emhttpDeviceId: String
"""The interface type of the disk"""
interfaceType: DiskInterfaceType!
@ -552,6 +547,7 @@ type Vars implements Node {
flashGuid: String
flashProduct: String
flashVendor: String
tpmGuid: String
regCheck: String
regFile: String
regGuid: String
@ -595,6 +591,7 @@ type Vars implements Node {
fsState: String
bootEligible: Boolean
enableBootTransfer: String
bootedFromFlashWithInternalBootSetup: Boolean
reservedNames: String
"""Human friendly string of array events happening"""
@ -632,6 +629,14 @@ enum ConfigErrorState {
WITHDRAWN
}
type ApiConfig {
version: String!
extraOrigins: [String!]!
sandbox: Boolean
ssoSubIds: [String!]!
plugins: [String!]!
}
type Permission {
resource: Resource!
@ -724,6 +729,65 @@ enum Role {
VIEWER
}
type NotificationCounts {
info: Int!
warning: Int!
alert: Int!
total: Int!
}
type NotificationOverview {
unread: NotificationCounts!
archive: NotificationCounts!
}
type Notification implements Node {
id: PrefixedID!
"""Also known as 'event'"""
title: String!
subject: String!
description: String!
importance: NotificationImportance!
link: String
type: NotificationType!
"""ISO Timestamp for when the notification occurred"""
timestamp: String
formattedTimestamp: String
}
enum NotificationImportance {
ALERT
INFO
WARNING
}
enum NotificationType {
UNREAD
ARCHIVE
}
type Notifications implements Node {
id: PrefixedID!
"""A cached overview of the notifications in the system & their severity."""
overview: NotificationOverview!
list(filter: NotificationFilter!): [Notification!]!
"""
Deduplicated list of unread warning and alert notifications, sorted latest first.
"""
warningsAndAlerts: [Notification!]!
}
input NotificationFilter {
importance: NotificationImportance
type: NotificationType!
offset: Int!
limit: Int!
}
type SsoSettings implements Node {
id: PrefixedID!
@ -758,7 +822,7 @@ interface FormSchema {
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
scalar JSON
type ApiKeyFormSettings implements Node & FormSchema {
id: PrefixedID!
@ -1049,6 +1113,9 @@ type Onboarding {
"""The activation code from the .activationcode file, if present"""
activationCode: String
"""Whether the onboarding modal should currently be shown"""
shouldOpen: Boolean!
"""Runtime onboarding state values used by the onboarding flow"""
onboardingState: OnboardingState!
}
@ -1064,10 +1131,13 @@ enum OnboardingStatus {
}
type Customization {
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ACTIVATION_CODE**"
activationCode: ActivationCode
"""Onboarding completion state and context"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CUSTOMIZATIONS**\n\n#### Description:\n\nOnboarding completion state and context"
onboarding: Onboarding!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DISPLAY**"
availableLanguages: [Language!]
}
@ -1078,6 +1148,18 @@ type OnboardingInternalBootResult {
output: String!
}
"""Current onboarding context for configuring internal boot"""
type OnboardingInternalBootContext {
arrayStopped: Boolean!
bootEligible: Boolean
bootedFromFlashWithInternalBootSetup: Boolean!
enableBootTransfer: String
reservedNames: [String!]!
shareNames: [String!]!
poolNames: [String!]!
assignableDisks: [Disk!]!
}
type RCloneDrive {
"""Provider name"""
name: String!
@ -1166,30 +1248,38 @@ type PluginInstallEvent {
}
type ArrayMutations {
"""Set array state"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nSet array state"
setState(input: ArrayStateInput!): UnraidArray!
"""Add new disk to array"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nAdd new disk to array"
addDiskToArray(input: ArrayDiskInput!): UnraidArray!
"""
Remove existing disk from array. NOTE: The array must be stopped before running this otherwise it'll throw an error.
"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nRemove existing disk from array. NOTE: The array must be stopped before running this otherwise it'll throw an error."
removeDiskFromArray(input: ArrayDiskInput!): UnraidArray!
"""Mount a disk in the array"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nMount a disk in the array"
mountArrayDisk(id: PrefixedID!): ArrayDisk!
"""Unmount a disk from the array"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nUnmount a disk from the array"
unmountArrayDisk(id: PrefixedID!): ArrayDisk!
"""Clear statistics for a disk in the array"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nClear statistics for a disk in the array"
clearArrayDiskStatistics(id: PrefixedID!): Boolean!
}
input ArrayStateInput {
"""Array state"""
desiredState: ArrayStateInputState!
"""
Optional password used to unlock encrypted array disks when starting the array
"""
decryptionPassword: String
"""
Optional keyfile contents used to unlock encrypted array disks when starting the array. Accepts a data URL or raw base64 payload.
"""
decryptionKeyfile: String
}
enum ArrayStateInputState {
@ -1206,31 +1296,31 @@ input ArrayDiskInput {
}
type DockerMutations {
"""Start a container"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nStart a container"
start(id: PrefixedID!): DockerContainer!
"""Stop a container"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nStop a container"
stop(id: PrefixedID!): DockerContainer!
"""Pause (Suspend) a container"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nPause (Suspend) a container"
pause(id: PrefixedID!): DockerContainer!
"""Unpause (Resume) a container"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nUnpause (Resume) a container"
unpause(id: PrefixedID!): DockerContainer!
"""Remove a container"""
"\n#### Required Permissions:\n\n- Action: **DELETE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nRemove a container"
removeContainer(id: PrefixedID!, withImage: Boolean): Boolean!
"""Update auto-start configuration for Docker containers"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nUpdate auto-start configuration for Docker containers"
updateAutostartConfiguration(entries: [DockerAutostartEntryInput!]!, persistUserPreferences: Boolean): Boolean!
"""Update a container to the latest image"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nUpdate a container to the latest image"
updateContainer(id: PrefixedID!): DockerContainer!
"""Update multiple containers to the latest images"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nUpdate multiple containers to the latest images"
updateContainers(ids: [PrefixedID!]!): [DockerContainer!]!
"""Update all containers that have available updates"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nUpdate all containers that have available updates"
updateAllContainers: [DockerContainer!]!
}
@ -1246,43 +1336,43 @@ input DockerAutostartEntryInput {
}
type VmMutations {
"""Start a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nStart a virtual machine"
start(id: PrefixedID!): Boolean!
"""Stop a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nStop a virtual machine"
stop(id: PrefixedID!): Boolean!
"""Pause a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nPause a virtual machine"
pause(id: PrefixedID!): Boolean!
"""Resume a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nResume a virtual machine"
resume(id: PrefixedID!): Boolean!
"""Force stop a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nForce stop a virtual machine"
forceStop(id: PrefixedID!): Boolean!
"""Reboot a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nReboot a virtual machine"
reboot(id: PrefixedID!): Boolean!
"""Reset a virtual machine"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nReset a virtual machine"
reset(id: PrefixedID!): Boolean!
}
"""API Key related mutations"""
type ApiKeyMutations {
"""Create an API key"""
"\n#### Required Permissions:\n\n- Action: **CREATE_ANY**\n- Resource: **API_KEY**\n\n#### Description:\n\nCreate an API key"
create(input: CreateApiKeyInput!): ApiKey!
"""Add a role to an API key"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **API_KEY**\n\n#### Description:\n\nAdd a role to an API key"
addRole(input: AddRoleForApiKeyInput!): Boolean!
"""Remove a role from an API key"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **API_KEY**\n\n#### Description:\n\nRemove a role from an API key"
removeRole(input: RemoveRoleFromApiKeyInput!): Boolean!
"""Delete one or more API keys"""
"\n#### Required Permissions:\n\n- Action: **DELETE_ANY**\n- Resource: **API_KEY**\n\n#### Description:\n\nDelete one or more API keys"
delete(input: DeleteApiKeyInput!): Boolean!
"""Update an API key"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **API_KEY**\n\n#### Description:\n\nUpdate an API key"
update(input: UpdateApiKeyInput!): ApiKey!
}
@ -1327,13 +1417,13 @@ input UpdateApiKeyInput {
"""Customization related mutations"""
type CustomizationMutations {
"""Update the UI theme (writes dynamix.cfg)"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CUSTOMIZATIONS**\n\n#### Description:\n\nUpdate the UI theme (writes dynamix.cfg)"
setTheme(
"""Theme to apply"""
theme: ThemeName!
): Theme!
"""Update the display locale (language)"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CUSTOMIZATIONS**\n\n#### Description:\n\nUpdate the display locale (language)"
setLocale(
"""Locale code to apply (e.g. en_US)"""
locale: String!
@ -1344,25 +1434,25 @@ type CustomizationMutations {
Parity check related mutations, WIP, response types and functionaliy will change
"""
type ParityCheckMutations {
"""Start a parity check"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nStart a parity check"
start(correct: Boolean!): JSON!
"""Pause a parity check"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nPause a parity check"
pause: JSON!
"""Resume a parity check"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nResume a parity check"
resume: JSON!
"""Cancel a parity check"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **ARRAY**\n\n#### Description:\n\nCancel a parity check"
cancel: JSON!
}
"""RClone related mutations"""
type RCloneMutations {
"""Create a new RClone remote"""
"\n#### Required Permissions:\n\n- Action: **CREATE_ANY**\n- Resource: **FLASH**\n\n#### Description:\n\nCreate a new RClone remote"
createRCloneRemote(input: CreateRCloneRemoteInput!): RCloneRemote!
"""Delete an existing RClone remote"""
"\n#### Required Permissions:\n\n- Action: **DELETE_ANY**\n- Resource: **FLASH**\n\n#### Description:\n\nDelete an existing RClone remote"
deleteRCloneRemote(input: DeleteRCloneRemoteInput!): Boolean!
}
@ -1378,20 +1468,35 @@ input DeleteRCloneRemoteInput {
"""Onboarding related mutations"""
type OnboardingMutations {
"""Mark onboarding as completed"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nMark onboarding as completed"
completeOnboarding: Onboarding!
"""Reset onboarding progress (for testing)"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nReset onboarding progress (for testing)"
resetOnboarding: Onboarding!
"""Override onboarding state for testing (in-memory only)"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nForce the onboarding modal open"
openOnboarding: Onboarding!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nClose the onboarding modal"
closeOnboarding: Onboarding!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nTemporarily bypass onboarding in API memory"
bypassOnboarding: Onboarding!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nClear the temporary onboarding bypass"
resumeOnboarding: Onboarding!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nOverride onboarding state for testing (in-memory only)"
setOnboardingOverride(input: OnboardingOverrideInput!): Onboarding!
"""Clear onboarding override state and reload from disk"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nClear onboarding override state and reload from disk"
clearOnboardingOverride: Onboarding!
"""Create and configure internal boot pool via emcmd operations"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nCreate and configure internal boot pool via emcmd operations"
createInternalBootPool(input: CreateInternalBootPoolInput!): OnboardingInternalBootResult!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nRefresh the internal boot onboarding context from the latest emhttp state"
refreshInternalBootContext: OnboardingInternalBootContext!
}
"""Onboarding override input for testing"""
@ -1406,6 +1511,7 @@ input OnboardingOverrideInput {
input OnboardingOverrideCompletionInput {
completed: Boolean
completedAtVersion: String
forceOpen: Boolean
}
"""Activation code override input"""
@ -1478,10 +1584,10 @@ input CreateInternalBootPoolInput {
"""Unraid plugin management mutations"""
type UnraidPluginsMutations {
"""Install an Unraid plugin and track installation progress"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nInstall an Unraid plugin and track installation progress"
installPlugin(input: InstallPluginInput!): PluginInstallOperation!
"""Install an Unraid language pack and track installation progress"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nInstall an Unraid language pack and track installation progress"
installLanguage(input: InstallPluginInput!): PluginInstallOperation!
}
@ -2121,36 +2227,40 @@ type DockerContainer implements Node {
autoStartWait: Int
templatePath: String
"""Project/Product homepage URL"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nProject/Product homepage URL"
projectUrl: String
"""Registry/Docker Hub URL"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nRegistry/Docker Hub URL"
registryUrl: String
"""Support page/thread URL"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nSupport page/thread URL"
supportUrl: String
"""Icon URL"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nIcon URL"
iconUrl: String
"""Resolved WebUI URL from template"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nResolved WebUI URL from template"
webUiUrl: String
"""Shell to use for console access (from template)"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nShell to use for console access (from template)"
shell: String
"""Port mappings from template (used when container is not running)"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nPort mappings from template (used when container is not running)"
templatePorts: [ContainerPort!]
"""Whether the container is orphaned (no template found)"""
isOrphaned: Boolean!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
isUpdateAvailable: Boolean
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
isRebuildReady: Boolean
"""Whether Tailscale is enabled for this container"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nWhether Tailscale is enabled for this container"
tailscaleEnabled: Boolean!
"""Tailscale status for this container (fetched via docker exec)"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nTailscale status for this container (fetched via docker exec)"
tailscaleStatus(forceRefresh: Boolean = false): TailscaleStatus
}
@ -2280,16 +2390,26 @@ type TailscaleStatus {
type Docker implements Node {
id: PrefixedID!
containers(skipCache: Boolean! = false @deprecated(reason: "Caching has been removed; this parameter is now ignored")): [DockerContainer!]!
networks(skipCache: Boolean! = false @deprecated(reason: "Caching has been removed; this parameter is now ignored")): [DockerNetwork!]!
portConflicts(skipCache: Boolean! = false @deprecated(reason: "Caching has been removed; this parameter is now ignored")): DockerPortConflicts!
"""
Access container logs. Requires specifying a target container id through resolver arguments.
"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
containers: [DockerContainer!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
networks: [DockerNetwork!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
portConflicts: DockerPortConflicts!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nAccess container logs. Requires specifying a target container id through resolver arguments."
logs(id: PrefixedID!, since: DateTime, tail: Int): DockerContainerLogs!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
container(id: PrefixedID!): DockerContainer
organizer(skipCache: Boolean! = false @deprecated(reason: "Caching has been removed; this parameter is now ignored")): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
organizer: ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
containerUpdateStatuses: [ExplicitStatusItem!]!
}
@ -2326,65 +2446,6 @@ type FlatOrganizerEntry {
meta: DockerContainer
}
type NotificationCounts {
info: Int!
warning: Int!
alert: Int!
total: Int!
}
type NotificationOverview {
unread: NotificationCounts!
archive: NotificationCounts!
}
type Notification implements Node {
id: PrefixedID!
"""Also known as 'event'"""
title: String!
subject: String!
description: String!
importance: NotificationImportance!
link: String
type: NotificationType!
"""ISO Timestamp for when the notification occurred"""
timestamp: String
formattedTimestamp: String
}
enum NotificationImportance {
ALERT
INFO
WARNING
}
enum NotificationType {
UNREAD
ARCHIVE
}
type Notifications implements Node {
id: PrefixedID!
"""A cached overview of the notifications in the system & their severity."""
overview: NotificationOverview!
list(filter: NotificationFilter!): [Notification!]!
"""
Deduplicated list of unread warning and alert notifications, sorted latest first.
"""
warningsAndAlerts: [Notification!]!
}
input NotificationFilter {
importance: NotificationImportance
type: NotificationType!
offset: Int!
limit: Int!
}
type FlashBackupStatus {
"""Status message indicating the outcome of the backup initiation."""
status: String!
@ -2613,14 +2674,6 @@ enum ServerStatus {
NEVER_CONNECTED
}
type ApiConfig {
version: String!
extraOrigins: [String!]!
sandbox: Boolean
ssoSubIds: [String!]!
plugins: [String!]!
}
type OidcAuthorizationRule {
"""The claim to check (e.g., email, sub, groups, hd)"""
claim: String!
@ -3129,59 +3182,110 @@ input AccessUrlObjectInput {
scalar PrefixedID
type Query {
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **API_KEY**"
apiKeys: [ApiKey!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **API_KEY**"
apiKey(id: PrefixedID!): ApiKey
"""All possible roles for API keys"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **PERMISSION**\n\n#### Description:\n\nAll possible roles for API keys"
apiKeyPossibleRoles: [Role!]!
"""All possible permissions for API keys"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **PERMISSION**\n\n#### Description:\n\nAll possible permissions for API keys"
apiKeyPossiblePermissions: [Permission!]!
"""Get the actual permissions that would be granted by a set of roles"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **PERMISSION**\n\n#### Description:\n\nGet the actual permissions that would be granted by a set of roles"
getPermissionsForRoles(roles: [Role!]!): [Permission!]!
"""
Preview the effective permissions for a combination of roles and explicit permissions
"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **PERMISSION**\n\n#### Description:\n\nPreview the effective permissions for a combination of roles and explicit permissions"
previewEffectivePermissions(roles: [Role!], permissions: [AddPermissionInput!]): [Permission!]!
"""Get all available authentication actions with possession"""
getAvailableAuthActions: [AuthAction!]!
"""Get JSON Schema for API key creation form"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **API_KEY**\n\n#### Description:\n\nGet JSON Schema for API key creation form"
getApiKeyCreationFormSchema: ApiKeyFormSettings!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**"
config: Config!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DISPLAY**"
display: InfoDisplay!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **FLASH**"
flash: Flash!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ME**"
me: UserAccount!
"""Get all notifications"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **NOTIFICATIONS**\n\n#### Description:\n\nGet all notifications"
notifications: Notifications!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ONLINE**"
online: Boolean!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **OWNER**"
owner: Owner!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **WELCOME**\n\n#### Description:\n\nGet the latest onboarding context for configuring internal boot"
internalBootContext: OnboardingInternalBootContext!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **REGISTRATION**"
registration: Registration
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **SERVERS**"
server: Server
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **SERVERS**"
servers: [Server!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **SERVICES**"
services: [Service!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **SHARE**"
shares: [Share!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **VARS**"
vars: Vars!
"""Get information about all VMs on the system"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **VMS**\n\n#### Description:\n\nGet information about all VMs on the system"
vms: Vms!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ARRAY**"
parityHistory: [ParityCheck!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ARRAY**"
array: UnraidArray!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CUSTOMIZATIONS**"
customization: Customization
"""Whether the system is a fresh install (no license key)"""
isFreshInstall: Boolean!
publicTheme: Theme!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **INFO**"
info: Info!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
docker: Docker!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DISK**"
disks: [Disk!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DISK**"
assignableDisks: [Disk!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DISK**"
disk(id: PrefixedID!): Disk!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **FLASH**"
rclone: RCloneBackupSettings!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **LOGS**"
logFiles: [LogFile!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **LOGS**"
logFile(path: String!, lines: Int, startLine: Int): LogFileContent!
settings: Settings!
isSSOEnabled: Boolean!
@ -3189,42 +3293,52 @@ type Query {
"""Get public OIDC provider information for login buttons"""
publicOidcProviders: [PublicOidcProvider!]!
"""Get all configured OIDC providers (admin only)"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nGet all configured OIDC providers (admin only)"
oidcProviders: [OidcProvider!]!
"""Get a specific OIDC provider by ID"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nGet a specific OIDC provider by ID"
oidcProvider(id: PrefixedID!): OidcProvider
"""Get the full OIDC configuration (admin only)"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nGet the full OIDC configuration (admin only)"
oidcConfiguration: OidcConfiguration!
"""Validate an OIDC session token (internal use for CLI validation)"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nValidate an OIDC session token (internal use for CLI validation)"
validateOidcSession(token: String!): OidcSessionValidation!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **INFO**"
metrics: Metrics!
"""Retrieve current system time configuration"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **VARS**\n\n#### Description:\n\nRetrieve current system time configuration"
systemTime: SystemTime!
"""Retrieve available time zone options"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nRetrieve available time zone options"
timeZoneOptions: [TimeZoneOption!]!
upsDevices: [UPSDevice!]!
upsDeviceById(id: String!): UPSDevice
upsConfiguration: UPSConfiguration!
"""Retrieve a plugin installation operation by identifier"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nRetrieve a plugin installation operation by identifier"
pluginInstallOperation(operationId: ID!): PluginInstallOperation
"""List all tracked plugin installation operations"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nList all tracked plugin installation operations"
pluginInstallOperations: [PluginInstallOperation!]!
"""List installed Unraid OS plugins by .plg filename"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nList installed Unraid OS plugins by .plg filename"
installedUnraidPlugins: [String!]!
"""List all installed plugins with their metadata"""
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nList all installed plugins with their metadata"
plugins: [Plugin!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONNECT**"
remoteAccess: RemoteAccess!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONNECT**"
connect: Connect!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **NETWORK**"
network: Network!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CLOUD**"
cloud: Cloud!
}
@ -3263,47 +3377,77 @@ type Mutation {
onboarding: OnboardingMutations!
unraidPlugins: UnraidPluginsMutations!
"""Update server name, comment, and model"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **SERVERS**\n\n#### Description:\n\nUpdate server name, comment, and model"
updateServerIdentity(name: String!, comment: String, sysModel: String): Server!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **VARS**"
updateSshSettings(input: UpdateSshInput!): Vars!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
createDockerFolder(name: String!, parentId: String, childrenIds: [String!]): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
setDockerFolderChildren(folderId: String, childrenIds: [String!]!): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
deleteDockerEntries(entryIds: [String!]!): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
moveDockerEntriesToFolder(sourceEntryIds: [String!]!, destinationFolderId: String!): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
moveDockerItemsToPosition(sourceEntryIds: [String!]!, destinationFolderId: String!, position: Float!): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
renameDockerFolder(folderId: String!, newName: String!): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
createDockerFolderWithItems(name: String!, parentId: String, sourceEntryIds: [String!], position: Float): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
updateDockerViewPreferences(viewId: String = "default", prefs: JSON!): ResolvedOrganizerV1!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
syncDockerTemplatePaths: DockerTemplateSyncResult!
"""
Reset Docker template mappings to defaults. Use this to recover from corrupted state.
"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**\n\n#### Description:\n\nReset Docker template mappings to defaults. Use this to recover from corrupted state."
resetDockerTemplateMappings: Boolean!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **DOCKER**"
refreshDockerDigests: Boolean!
"""Initiates a flash drive backup using a configured remote."""
initiateFlashBackup(input: InitiateFlashBackupInput!): FlashBackupStatus!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONFIG**"
updateSettings(input: JSON!): UpdateSettingsResponse!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **INFO**"
updateTemperatureConfig(input: TemperatureConfigInput!): Boolean!
"""Update system time configuration"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nUpdate system time configuration"
updateSystemTime(input: UpdateSystemTimeInput!): SystemTime!
configureUps(config: UPSConfigInput!): Boolean!
"""
Add one or more plugins to the API. Returns false if restart was triggered automatically, true if manual restart is required.
"""
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nAdd one or more plugins to the API. Returns false if restart was triggered automatically, true if manual restart is required."
addPlugin(input: PluginManagementInput!): Boolean!
"""
Remove one or more plugins from the API. Returns false if restart was triggered automatically, true if manual restart is required.
"""
"\n#### Required Permissions:\n\n- Action: **DELETE_ANY**\n- Resource: **CONFIG**\n\n#### Description:\n\nRemove one or more plugins from the API. Returns false if restart was triggered automatically, true if manual restart is required."
removePlugin(input: PluginManagementInput!): Boolean!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONFIG**"
updateApiSettings(input: ConnectSettingsInput!): ConnectSettingsValues!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONNECT**"
connectSignIn(input: ConnectSignInInput!): Boolean!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONNECT**"
connectSignOut: Boolean!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONNECT**"
setupRemoteAccess(input: SetupRemoteAccessInput!): Boolean!
"\n#### Required Permissions:\n\n- Action: **UPDATE_ANY**\n- Resource: **CONNECT__REMOTE_ACCESS**"
enableDynamicRemoteAccess(input: EnableDynamicRemoteAccessInput!): Boolean!
}
@ -3551,20 +3695,49 @@ input AccessUrlInput {
}
type Subscription {
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DISPLAY**"
displaySubscription: InfoDisplay!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **NOTIFICATIONS**"
notificationAdded: Notification!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **NOTIFICATIONS**"
notificationsOverview: NotificationOverview!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **NOTIFICATIONS**"
notificationsWarningsAndAlerts: [Notification!]!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **OWNER**"
ownerSubscription: Owner!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **SERVERS**"
serversSubscription: Server!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ARRAY**"
parityHistorySubscription: ParityCheck!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **ARRAY**"
arraySubscription: UnraidArray!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **DOCKER**"
dockerContainerStats: DockerContainerStats!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **LOGS**"
logFile(path: String!): LogFileContent!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **INFO**"
systemMetricsCpu: CpuUtilization!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **INFO**"
systemMetricsCpuTelemetry: CpuPackages!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **INFO**"
systemMetricsMemory: MemoryUtilization!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **INFO**"
systemMetricsTemperature: TemperatureMetrics
upsUpdates: UPSDevice!
"\n#### Required Permissions:\n\n- Action: **READ_ANY**\n- Resource: **CONFIG**"
pluginInstallUpdates(operationId: ID!): PluginInstallEvent!
}

View file

@ -68,11 +68,11 @@ The Unraid API enforces approximately 100 requests per 10 seconds. The MCP serve
## GraphQL schema reference
The full Unraid GraphQL schema is available in:
- `docs/unraid-schema.graphql` -- Complete schema definition (74 KB)
- `docs/unraid-api-introspection.json` -- Introspection result (236 KB)
- `docs/UNRAID_API_COMPLETE_REFERENCE.md` -- Human-readable reference (73 KB)
- `docs/UNRAID_API_OPERATIONS.md` -- All supported operations with examples
- `docs/UNRAID_API_REFERENCE.md` -- Condensed reference
- `docs/unraid/UNRAID-API-SUMMARY.md` -- Condensed overview
- `docs/unraid/UNRAID-API-CHANGES.md` -- Diff against the prior introspection snapshot
- `docs/unraid/UNRAID-SCHEMA.graphql` -- Complete schema definition
- `docs/unraid/UNRAID-API-INTROSPECTION.json` -- Introspection result
- `docs/unraid/UNRAID-API-COMPLETE-REFERENCE.md` -- Human-readable reference
### Query organization

View file

@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Generate a complete Markdown reference from Unraid GraphQL introspection."""
"""Generate canonical Unraid GraphQL docs from live introspection."""
from __future__ import annotations
import argparse
import datetime as dt
import json
import os
from collections import Counter, defaultdict
@ -11,9 +12,17 @@ from pathlib import Path
from typing import Any
import httpx
from graphql import build_client_schema
from graphql import print_schema
DEFAULT_OUTPUT = Path("docs/UNRAID_API_COMPLETE_REFERENCE.md")
DOCS_DIR = Path("docs/unraid")
DEFAULT_COMPLETE_OUTPUT = DOCS_DIR / "UNRAID-API-COMPLETE-REFERENCE.md"
DEFAULT_SUMMARY_OUTPUT = DOCS_DIR / "UNRAID-API-SUMMARY.md"
DEFAULT_INTROSPECTION_OUTPUT = DOCS_DIR / "UNRAID-API-INTROSPECTION.json"
DEFAULT_SCHEMA_OUTPUT = DOCS_DIR / "UNRAID-SCHEMA.graphql"
DEFAULT_CHANGES_OUTPUT = DOCS_DIR / "UNRAID-API-CHANGES.md"
LEGACY_INTROSPECTION_OUTPUT = Path("docs/unraid-api-introspection.json")
INTROSPECTION_QUERY = """
query FullIntrospection {
@ -371,10 +380,329 @@ def _build_markdown(schema: dict[str, Any], *, include_introspection: bool) -> s
return "\n".join(lines).rstrip() + "\n"
def _visible_types(
schema: dict[str, Any], *, include_introspection: bool = False
) -> list[dict[str, Any]]:
"""Return visible types from the schema."""
types = schema.get("types") or []
return [
item
for item in types
if item.get("name") and (include_introspection or not str(item["name"]).startswith("__"))
]
def _types_by_name(
schema: dict[str, Any], *, include_introspection: bool = False
) -> dict[str, dict[str, Any]]:
"""Map visible types by name."""
return {
str(item["name"]): item
for item in _visible_types(schema, include_introspection=include_introspection)
}
def _field_signature(field: dict[str, Any]) -> str:
"""Render a stable field signature for change detection."""
args = sorted(field.get("args") or [], key=lambda item: str(item["name"]))
rendered_args = []
for arg in args:
arg_sig = f"{arg['name']}: {_type_to_str(arg.get('type'))}"
if arg.get("defaultValue") is not None:
arg_sig += f" = {arg['defaultValue']}"
rendered_args.append(arg_sig)
args_section = f"({', '.join(rendered_args)})" if rendered_args else "()"
return f"{field['name']}{args_section}: {_type_to_str(field.get('type'))}"
def _input_field_signature(field: dict[str, Any]) -> str:
"""Render a stable input field signature for change detection."""
signature = f"{field['name']}: {_type_to_str(field.get('type'))}"
if field.get("defaultValue") is not None:
signature += f" = {field['defaultValue']}"
return signature
def _enum_value_signature(enum_value: dict[str, Any]) -> str:
"""Render a stable enum value signature for change detection."""
signature = str(enum_value["name"])
if enum_value.get("isDeprecated"):
reason = _clean(enum_value.get("deprecationReason"))
signature += f" [deprecated: {reason}]" if reason else " [deprecated]"
return signature
def _root_field_names(schema: dict[str, Any], root_key: str) -> set[str]:
"""Return root field names for query/mutation/subscription."""
root_type = (schema.get(root_key) or {}).get("name")
if not root_type:
return set()
types = _types_by_name(schema)
root = types.get(str(root_type))
if not root:
return set()
return {str(field["name"]) for field in (root.get("fields") or [])}
def _type_member_signatures(type_info: dict[str, Any]) -> set[str]:
"""Return stable member signatures for a type."""
kind = str(type_info.get("kind", "UNKNOWN"))
if kind in {"OBJECT", "INTERFACE"}:
return {_field_signature(field) for field in (type_info.get("fields") or [])}
if kind == "INPUT_OBJECT":
return {_input_field_signature(field) for field in (type_info.get("inputFields") or [])}
if kind == "ENUM":
return {_enum_value_signature(value) for value in (type_info.get("enumValues") or [])}
if kind == "UNION":
return {
str(possible["name"])
for possible in (type_info.get("possibleTypes") or [])
if possible.get("name")
}
return set()
def _build_summary_markdown(
schema: dict[str, Any], *, source: str, generated_at: str, include_introspection: bool
) -> str:
"""Build condensed root-level summary markdown."""
types = _types_by_name(schema, include_introspection=include_introspection)
visible_types = _visible_types(schema, include_introspection=include_introspection)
directives = sorted(schema.get("directives") or [], key=lambda item: str(item["name"]))
kind_counts = Counter(str(item.get("kind", "UNKNOWN")) for item in visible_types)
query_root = (schema.get("queryType") or {}).get("name")
mutation_root = (schema.get("mutationType") or {}).get("name")
subscription_root = (schema.get("subscriptionType") or {}).get("name")
lines = [
"# Unraid API Introspection Summary",
"",
f"> Auto-generated from live API introspection on {generated_at}",
f"> Source: {source}",
"",
"## Table of Contents",
"",
"- [Schema Summary](#schema-summary)",
"- [Query Fields](#query-fields)",
"- [Mutation Fields](#mutation-fields)",
"- [Subscription Fields](#subscription-fields)",
"- [Type Kinds](#type-kinds)",
"",
"## Schema Summary",
f"- Query root: `{query_root}`",
f"- Mutation root: `{mutation_root}`",
f"- Subscription root: `{subscription_root}`",
f"- Total types: **{len(visible_types)}**",
f"- Total directives: **{len(directives)}**",
"",
]
def render_table(section_title: str, root_name: str | None) -> None:
lines.append(f"## {section_title}")
lines.append("")
lines.append("| Field | Return Type | Arguments |")
lines.append("|-------|-------------|-----------|")
root = types.get(str(root_name)) if root_name else None
for field in sorted(root.get("fields") or [], key=lambda item: str(item["name"])) if root else []:
args = sorted(field.get("args") or [], key=lambda item: str(item["name"]))
arg_text = (
""
if not args
else ", ".join(
(
f"{arg['name']}: {_type_to_str(arg.get('type'))}"
+ (
f" (default: {arg['defaultValue']})"
if arg.get("defaultValue") is not None
else ""
)
)
for arg in args
)
)
lines.append(
f"| `{field['name']}` | `{_type_to_str(field.get('type'))}` | {arg_text} |"
)
lines.append("")
render_table("Query Fields", query_root)
render_table("Mutation Fields", mutation_root)
render_table("Subscription Fields", subscription_root)
lines.append("## Type Kinds")
lines.append("")
for kind in sorted(kind_counts):
lines.append(f"- `{kind}`: {kind_counts[kind]}")
lines.extend(
[
"",
"## Notes",
"",
"- This summary is intentionally condensed; the full schema reference lives in `UNRAID-API-COMPLETE-REFERENCE.md`.",
"- Raw schema exports live in `UNRAID-API-INTROSPECTION.json` and `UNRAID-SCHEMA.graphql`.",
"",
]
)
return "\n".join(lines)
def _build_changes_markdown(
previous_schema: dict[str, Any] | None,
current_schema: dict[str, Any],
*,
source: str,
generated_at: str,
include_introspection: bool,
) -> str:
"""Build a schema change report from a previous introspection snapshot."""
lines = [
"# Unraid API Schema Changes",
"",
f"> Generated on {generated_at}",
f"> Source: {source}",
"",
]
if previous_schema is None:
lines.extend(
[
"No previous introspection snapshot was available, so no diff could be computed.",
"",
"The current canonical artifacts were regenerated successfully.",
"",
]
)
return "\n".join(lines)
current_types = _types_by_name(current_schema, include_introspection=include_introspection)
previous_types = _types_by_name(previous_schema, include_introspection=include_introspection)
sections = [
("Query fields", _root_field_names(previous_schema, "queryType"), _root_field_names(current_schema, "queryType")),
(
"Mutation fields",
_root_field_names(previous_schema, "mutationType"),
_root_field_names(current_schema, "mutationType"),
),
(
"Subscription fields",
_root_field_names(previous_schema, "subscriptionType"),
_root_field_names(current_schema, "subscriptionType"),
),
]
all_kinds = {"OBJECT", "INPUT_OBJECT", "ENUM", "INTERFACE", "UNION", "SCALAR"}
previous_by_kind = {
kind: {name for name, info in previous_types.items() if str(info.get("kind")) == kind}
for kind in all_kinds
}
current_by_kind = {
kind: {name for name, info in current_types.items() if str(info.get("kind")) == kind}
for kind in all_kinds
}
for label, old_set, new_set in sections:
added = sorted(new_set - old_set)
removed = sorted(old_set - new_set)
lines.append(f"## {label}")
lines.append("")
lines.append(f"- Added: {len(added)}")
if added:
lines.extend(f" - `{name}`" for name in added)
lines.append(f"- Removed: {len(removed)}")
if removed:
lines.extend(f" - `{name}`" for name in removed)
if not added and not removed:
lines.append("- No changes")
lines.append("")
lines.append("## Type Changes")
lines.append("")
for kind in sorted(all_kinds):
added = sorted(current_by_kind[kind] - previous_by_kind[kind])
removed = sorted(previous_by_kind[kind] - current_by_kind[kind])
if not added and not removed:
continue
lines.append(f"### {kind}")
lines.append("")
lines.append(f"- Added: {len(added)}")
if added:
lines.extend(f" - `{name}`" for name in added)
lines.append(f"- Removed: {len(removed)}")
if removed:
lines.extend(f" - `{name}`" for name in removed)
lines.append("")
changed_types: list[str] = []
for name in sorted(set(previous_types) & set(current_types)):
previous_info = previous_types[name]
current_info = current_types[name]
if str(previous_info.get("kind")) != str(current_info.get("kind")):
changed_types.append(name)
continue
if _type_member_signatures(previous_info) != _type_member_signatures(current_info):
changed_types.append(name)
lines.append("## Type Signature Changes")
lines.append("")
if not changed_types:
lines.append("No existing type signatures changed.")
lines.append("")
return "\n".join(lines)
for name in changed_types:
previous_info = previous_types[name]
current_info = current_types[name]
previous_members = _type_member_signatures(previous_info)
current_members = _type_member_signatures(current_info)
added = sorted(current_members - previous_members)
removed = sorted(previous_members - current_members)
lines.append(f"### `{name}` ({current_info.get('kind')})")
lines.append("")
lines.append(f"- Added members: {len(added)}")
if added:
lines.extend(f" - `{member}`" for member in added)
lines.append(f"- Removed members: {len(removed)}")
if removed:
lines.extend(f" - `{member}`" for member in removed)
if not added and not removed and previous_info.get("kind") != current_info.get("kind"):
lines.append(
f"- Kind changed: `{previous_info.get('kind')}` -> `{current_info.get('kind')}`"
)
lines.append("")
return "\n".join(lines)
def _extract_schema(payload: dict[str, Any]) -> dict[str, Any]:
"""Return the __schema payload or raise."""
schema = (payload.get("data") or {}).get("__schema")
if not schema:
raise SystemExit("GraphQL introspection returned no __schema payload.")
return schema
def _load_previous_schema(path: Path) -> dict[str, Any] | None:
"""Load a prior introspection snapshot if available."""
if not path.exists():
return None
payload = json.loads(path.read_text(encoding="utf-8"))
return _extract_schema(payload)
def _write_schema_graphql(path: Path, payload: dict[str, Any]) -> None:
"""Write SDL schema output."""
schema_graphql = print_schema(build_client_schema(payload["data"]))
banner = (
"# ------------------------------------------------------\n"
"# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)\n"
"# ------------------------------------------------------\n\n"
)
path.write_text(banner + schema_graphql.rstrip() + "\n", encoding="utf-8")
def _parse_args() -> argparse.Namespace:
"""Parse CLI args."""
parser = argparse.ArgumentParser(
description="Generate complete Unraid GraphQL schema reference Markdown from introspection."
description="Generate canonical Unraid GraphQL docs from introspection."
)
parser.add_argument(
"--api-url",
@ -387,10 +715,43 @@ def _parse_args() -> argparse.Namespace:
help="API key (default: UNRAID_API_KEY env var).",
)
parser.add_argument(
"--output",
"--complete-output",
type=Path,
default=DEFAULT_OUTPUT,
help=f"Output markdown file path (default: {DEFAULT_OUTPUT}).",
default=DEFAULT_COMPLETE_OUTPUT,
help=f"Full reference output path (default: {DEFAULT_COMPLETE_OUTPUT}).",
)
parser.add_argument(
"--summary-output",
type=Path,
default=DEFAULT_SUMMARY_OUTPUT,
help=f"Summary output path (default: {DEFAULT_SUMMARY_OUTPUT}).",
)
parser.add_argument(
"--introspection-output",
type=Path,
default=DEFAULT_INTROSPECTION_OUTPUT,
help=f"Introspection JSON output path (default: {DEFAULT_INTROSPECTION_OUTPUT}).",
)
parser.add_argument(
"--schema-output",
type=Path,
default=DEFAULT_SCHEMA_OUTPUT,
help=f"SDL schema output path (default: {DEFAULT_SCHEMA_OUTPUT}).",
)
parser.add_argument(
"--changes-output",
type=Path,
default=DEFAULT_CHANGES_OUTPUT,
help=f"Schema changes report path (default: {DEFAULT_CHANGES_OUTPUT}).",
)
parser.add_argument(
"--previous-introspection",
type=Path,
default=None,
help=(
"Previous introspection JSON used for diffing. Defaults to the current "
"introspection output path, falling back to the legacy docs path if present."
),
)
parser.add_argument(
"--timeout-seconds",
@ -420,7 +781,7 @@ def main() -> int:
if not args.api_key:
raise SystemExit("Missing API key. Provide --api-key or set UNRAID_API_KEY.")
headers = {"Authorization": f"Bearer {args.api_key}", "Content-Type": "application/json"}
headers = {"x-api-key": args.api_key, "Content-Type": "application/json"}
with httpx.Client(timeout=args.timeout_seconds, verify=args.verify_ssl) as client:
response = client.post(args.api_url, json={"query": INTROSPECTION_QUERY}, headers=headers)
@ -431,15 +792,53 @@ def main() -> int:
errors = json.dumps(payload["errors"], indent=2)
raise SystemExit(f"GraphQL introspection returned errors:\n{errors}")
schema = (payload.get("data") or {}).get("__schema")
if not schema:
raise SystemExit("GraphQL introspection returned no __schema payload.")
schema = _extract_schema(payload)
generated_at = dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat()
previous_path = (
args.previous_introspection
or (args.introspection_output if args.introspection_output.exists() else LEGACY_INTROSPECTION_OUTPUT)
)
previous_schema = _load_previous_schema(previous_path)
markdown = _build_markdown(schema, include_introspection=bool(args.include_introspection_types))
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(markdown, encoding="utf-8")
for path in {
args.complete_output,
args.summary_output,
args.introspection_output,
args.schema_output,
args.changes_output,
}:
path.parent.mkdir(parents=True, exist_ok=True)
print(f"Wrote {args.output}")
full_reference = _build_markdown(
schema, include_introspection=bool(args.include_introspection_types)
)
summary = _build_summary_markdown(
schema,
source=args.api_url,
generated_at=generated_at,
include_introspection=bool(args.include_introspection_types),
)
changes = _build_changes_markdown(
previous_schema,
schema,
source=args.api_url,
generated_at=generated_at,
include_introspection=bool(args.include_introspection_types),
)
args.complete_output.write_text(full_reference, encoding="utf-8")
args.summary_output.write_text(summary, encoding="utf-8")
args.introspection_output.write_text(
json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
_write_schema_graphql(args.schema_output, payload)
args.changes_output.write_text(changes, encoding="utf-8")
print(f"Wrote {args.complete_output}")
print(f"Wrote {args.summary_output}")
print(f"Wrote {args.introspection_output}")
print(f"Wrote {args.schema_output}")
print(f"Wrote {args.changes_output}")
return 0

View file

@ -500,8 +500,8 @@ class TestDockerToolRequests:
nonlocal call_count
body = json.loads(request.content.decode())
call_count += 1
if "skipCache" in body["query"]:
# Resolution query: docker { containers(skipCache: true) { id names } }
if "ResolveContainerID" in body["query"]:
# Resolution query: docker { containers { id names } }
return _graphql_response(
{"docker": {"containers": [{"id": resolved_id, "names": ["plex"]}]}}
)

View file

@ -11,7 +11,7 @@ import pytest
from graphql import DocumentNode, GraphQLSchema, build_schema, parse, validate
SCHEMA_PATH = Path(__file__).resolve().parents[2] / "docs" / "unraid-schema.graphql"
SCHEMA_PATH = Path(__file__).resolve().parents[2] / "docs" / "unraid" / "UNRAID-SCHEMA.graphql"
@pytest.fixture(scope="module")
@ -820,7 +820,7 @@ class TestHealthQueries:
overview { unread { alert warning total } }
}
docker {
containers(skipCache: true) { id state status }
containers { id state status }
}
}
"""

View file

@ -0,0 +1,154 @@
from __future__ import annotations
from scripts.generate_unraid_api_reference import _build_changes_markdown
def _type_ref(name: str | None = None, *, kind: str = "OBJECT", of_type: dict | None = None) -> dict:
return {"kind": kind, "name": name, "ofType": of_type}
def _field(
name: str,
return_type: dict,
*,
args: list[dict] | None = None,
description: str | None = None,
) -> dict:
return {
"name": name,
"description": description,
"isDeprecated": False,
"deprecationReason": None,
"args": args or [],
"type": return_type,
}
def _arg(name: str, arg_type: dict, *, default: str | None = None) -> dict:
return {
"name": name,
"description": None,
"defaultValue": default,
"type": arg_type,
}
def _schema(
*,
query_fields: list[dict],
mutation_fields: list[dict] | None = None,
subscription_fields: list[dict] | None = None,
extra_types: list[dict] | None = None,
) -> dict:
types = [
{"kind": "OBJECT", "name": "Query", "description": None, "fields": query_fields, "inputFields": None, "interfaces": [], "enumValues": None, "possibleTypes": None},
{"kind": "OBJECT", "name": "Mutation", "description": None, "fields": mutation_fields or [], "inputFields": None, "interfaces": [], "enumValues": None, "possibleTypes": None},
{"kind": "OBJECT", "name": "Subscription", "description": None, "fields": subscription_fields or [], "inputFields": None, "interfaces": [], "enumValues": None, "possibleTypes": None},
]
if extra_types:
types.extend(extra_types)
return {
"queryType": {"name": "Query"},
"mutationType": {"name": "Mutation"},
"subscriptionType": {"name": "Subscription"},
"directives": [],
"types": types,
}
def test_changes_report_handles_missing_previous_snapshot() -> None:
current = _schema(query_fields=[_field("online", _type_ref("Boolean", kind="SCALAR"))])
report = _build_changes_markdown(
None,
current,
source="https://tower.local/graphql",
generated_at="2026-04-05T18:00:00+00:00",
include_introspection=False,
)
assert "No previous introspection snapshot was available" in report
assert "https://tower.local/graphql" in report
def test_changes_report_lists_root_and_type_signature_deltas() -> None:
previous = _schema(
query_fields=[
_field("online", _type_ref("Boolean", kind="SCALAR")),
_field("server", _type_ref("Server")),
],
mutation_fields=[_field("connectSignIn", _type_ref("Boolean", kind="SCALAR"))],
subscription_fields=[_field("ownerSubscription", _type_ref("Owner"))],
extra_types=[
{
"kind": "OBJECT",
"name": "Server",
"description": None,
"fields": [_field("id", _type_ref("ID", kind="SCALAR"))],
"inputFields": None,
"interfaces": [],
"enumValues": None,
"possibleTypes": None,
}
],
)
current = _schema(
query_fields=[
_field("online", _type_ref("Boolean", kind="SCALAR")),
_field(
"server",
_type_ref("Server"),
args=[_arg("verbose", _type_ref("Boolean", kind="SCALAR"), default="false")],
),
_field("cloud", _type_ref("Cloud")),
],
mutation_fields=[],
subscription_fields=[
_field("ownerSubscription", _type_ref("Owner")),
_field("dockerContainerStats", _type_ref("DockerContainerStats")),
],
extra_types=[
{
"kind": "OBJECT",
"name": "Server",
"description": None,
"fields": [
_field("id", _type_ref("ID", kind="SCALAR")),
_field("name", _type_ref("String", kind="SCALAR")),
],
"inputFields": None,
"interfaces": [],
"enumValues": None,
"possibleTypes": None,
},
{
"kind": "OBJECT",
"name": "Cloud",
"description": None,
"fields": [],
"inputFields": None,
"interfaces": [],
"enumValues": None,
"possibleTypes": None,
},
],
)
report = _build_changes_markdown(
previous,
current,
source="https://tower.local/graphql",
generated_at="2026-04-05T18:00:00+00:00",
include_introspection=False,
)
assert "## Query fields" in report
assert "`cloud`" in report
assert "## Mutation fields" in report
assert "`connectSignIn`" in report
assert "## Subscription fields" in report
assert "`dockerContainerStats`" in report
assert "### OBJECT" in report
assert "### `Server` (OBJECT)" in report
assert "`name(): String`" in report
assert "`server(verbose: Boolean = false): Server`" in report

View file

@ -18,15 +18,15 @@ from ..core.utils import safe_get
# ===========================================================================
_DOCKER_QUERIES: dict[str, str] = {
"list": "query ListDockerContainers { docker { containers(skipCache: false) { id names image state status autoStart } } }",
"details": "query GetContainerDetails { docker { containers(skipCache: false) { id names image imageId command created ports { ip privatePort publicPort type } sizeRootFs labels state status hostConfig { networkMode } networkSettings mounts autoStart } } }",
"list": "query ListDockerContainers { docker { containers { id names image state status autoStart } } }",
"details": "query GetContainerDetails { docker { containers { id names image imageId command created ports { ip privatePort publicPort type } sizeRootFs labels state status hostConfig { networkMode } networkSettings mounts autoStart } } }",
"networks": "query GetDockerNetworks { docker { networks { id name driver scope } } }",
"network_details": "query GetDockerNetwork { docker { networks { id name driver scope enableIPv6 internal attachable containers options labels } } }",
}
# Internal query used only for container ID resolution — not a public subaction.
_DOCKER_RESOLVE_QUERY = (
"query ResolveContainerID { docker { containers(skipCache: true) { id names } } }"
"query ResolveContainerID { docker { containers { id names } } }"
)
_DOCKER_MUTATIONS: dict[str, str] = {

View file

@ -31,7 +31,7 @@ _HEALTH_QUERIES: dict[str, str] = {
" info { machineId time versions { core { unraid } } os { uptime } }"
" array { state }"
" notifications { overview { unread { alert warning total } } }"
" docker { containers(skipCache: true) { id state status } }"
" docker { containers { id state status } }"
" }"
),
}

View file

@ -1572,7 +1572,7 @@ wheels = [
[[package]]
name = "unraid-mcp"
version = "1.2.2"
version = "1.2.4"
source = { editable = "." }
dependencies = [
{ name = "fastapi" },