# Phase 14: Unraid API Access - Research **Researched:** 2026-02-08 **Domain:** Unraid GraphQL API integration from n8n container **Confidence:** HIGH ## Summary Phase 14 establishes connectivity from the n8n container to Unraid's GraphQL API endpoint and validates authentication, enabling subsequent phases to sync container update status. This phase is pure infrastructure setup — no workflow modifications, no mutations called, no status synchronization implemented yet. The Unraid GraphQL API is the official interface for Docker container management, available natively in Unraid 7.2+ or via the Connect plugin for 6.9-7.1. Authentication uses API keys with granular permission scoping. The API endpoint is `http:///graphql` and uses standard GraphQL over HTTP with x-api-key header authentication. Network access from the n8n container to the Unraid host requires either `host.docker.internal` (with `--add-host=host.docker.internal:host-gateway` in container config) or direct IP/hostname access. Container ID format uses Unraid's `PrefixedID` scalar type (e.g., `docker:containername` or server-prefixed format like `123:containername`). **Primary recommendation:** Use `host.docker.internal` for network access (standard Docker pattern), store credentials in `.env.unraid-api` file (mirrors existing n8n API pattern), and validate with a simple container list query before planning mutation calls in Phase 15. --- ## User Constraints (from CONTEXT.md) ### Locked Decisions **Credential management** - Dual storage: `.env.unraid-api` file (for CLI/deploy scripts) + n8n credential (for workflow nodes) - `.env.unraid-api` contains both `UNRAID_API_KEY` and `UNRAID_HOST` (mirrors `.env.n8n-api` pattern) - Phase includes documentation for creating the Unraid API key in WebGUI with correct permissions - File is gitignored **Network connectivity** - User runs Unraid on default WebGUI ports — GraphQL API assumed on same port - HTTP vs HTTPS and self-signed cert handling needs to be figured out during research/testing - Connectivity verification done purely through n8n workflow nodes — no separate test script - Host URL stored in `UNRAID_HOST` env var — user configures during setup **GraphQL query design** - Document only the queries/mutations relevant to update sync — not the full Docker schema - Container ID format mapping needs research — we don't know how Unraid identifies containers in GraphQL yet - GraphQL API contract documented in ARCHITECTURE.md alongside existing Docker API contract **Error handling & feedback** - Clear, descriptive error messages on failure (what failed, why, what to check) - Use existing error logging system — same pattern as Docker API errors, not a separate log category - Phase 14 tests connectivity only — permission validation happens naturally in Phase 15 when mutations are attempted - No user-facing Telegram health check command for Unraid API — errors surface naturally during sync failures ### Claude's Discretion - n8n credential type selection (based on Unraid GraphQL API's actual auth mechanism) - Network approach for n8n container reaching Unraid host (host IP, Docker networking, etc.) - What fields to include in the test query (based on what downstream phases need) ### Deferred Ideas (OUT OF SCOPE) None — discussion stayed within phase scope --- ## Standard Stack ### Core | Library | Version | Purpose | Why Standard | |---------|---------|---------|--------------| | Unraid GraphQL API | 7.2+ native, 6.9-7.1 via Connect plugin | Container management and status sync | Official Unraid interface, same mechanism as WebGUI | | n8n HTTP Request node | Built-in (n8n 1.x) | GraphQL client | Standard GraphQL-over-HTTP pattern, no additional dependencies | | Unraid API Key | N/A (credential) | Authentication | Official authentication method, granular permission scoping | ### Supporting | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | n8n Credential system | Built-in | Secure credential storage | Store `UNRAID_HOST` and `UNRAID_API_KEY` for workflow access | | `.env.unraid-api` file | N/A (file) | External credential storage | CLI testing, deployment scripts, credential documentation | ### Alternatives Considered | Instead of | Could Use | Tradeoff | |------------|-----------|----------| | GraphQL API | Direct file write to `/var/lib/docker/unraid-update-status.json` | File format undocumented, brittle, no UI refresh trigger | | API key auth | Session cookies | Requires WebGUI login flow, session expiry handling, fragile | | `host.docker.internal` | Hardcoded Unraid IP | Works but not portable, requires manual config changes | **Installation:** No n8n dependencies required (HTTP Request node is built-in). Requires Unraid API key creation on host: ```bash # SSH to Unraid: unraid-api apikey --create \ --name "Docker Manager Bot" \ --permissions "DOCKER:UPDATE_ANY" \ --description "Telegram bot container update sync" \ --json ``` **Network configuration:** If using `host.docker.internal` (recommended), add to n8n container config via Unraid Docker template editor: ``` Extra Parameters: --add-host=host.docker.internal:host-gateway ``` --- ## Architecture Patterns ### Recommended Credential Storage Pattern **Dual storage approach** (mirrors existing `.env.n8n-api` pattern): ```bash # .env.unraid-api (gitignored) UNRAID_HOST=http://host.docker.internal UNRAID_API_KEY=your-api-key-here ``` **n8n credential configuration:** 1. Credentials → Add Credential → Header Auth 2. Name: `Unraid API Key` 3. Header Name: `x-api-key` 4. Header Value: Reference environment variable or paste key directly **Why dual storage:** CLI scripts (testing, deployment verification) use `.env` file, n8n workflows use n8n credential system. Keeps patterns consistent with existing n8n API access. ### Network Access Pattern **Option 1: host.docker.internal (RECOMMENDED)** ```javascript // n8n HTTP Request node configuration { url: 'http://host.docker.internal/graphql', method: 'POST', authentication: 'headerAuth', credentialName: 'Unraid API Key', body: { query: 'query { docker { containers { id names state } } }' } } ``` **Requires:** `--add-host=host.docker.internal:host-gateway` in n8n container config **Pros:** Portable (works across different Unraid IPs), standard Docker pattern **Cons:** Requires container reconfiguration (one-time setup) **Option 2: Direct IP/Hostname** ```javascript // Alternative for environments where host.docker.internal doesn't work { url: 'http://192.168.1.100/graphql', // or http://tower.local/graphql // ... rest same as Option 1 } ``` **Pros:** No container reconfiguration needed **Cons:** Hardcoded, not portable if Unraid IP changes **Recommendation:** Use `host.docker.internal` for primary implementation, document direct IP as fallback. ### GraphQL Query Pattern for Testing **Test connectivity query:** ```graphql query TestConnectivity { docker { containers { id names state isUpdateAvailable } } } ``` **What this validates:** - Network connectivity (can reach `/graphql` endpoint) - Authentication (API key accepted) - Permissions (can read Docker data) - Container ID format (see actual ID structure in response) **n8n HTTP Request node body:** ```json { "query": "query { docker { containers { id names state isUpdateAvailable } } }" } ``` ### Container ID Format Documentation Pattern **Research finding:** Unraid uses `PrefixedID` scalar type. Format is `:` where server_id is stripped on input, added on output. **Example observed formats:** - `docker:plex` (name-based, common pattern) - `123:456` (numeric server + resource ID) **Critical:** Actual format must be validated during Phase 14 testing. The GraphQL schema defines `PrefixedID` as abstract — implementation details discovered by running test query. **Documentation location:** `ARCHITECTURE.md` section "GraphQL API Contract" (to be added in Phase 14). ### Anti-Patterns to Avoid - **Hardcoding credentials in workflow nodes:** Always use n8n credential system or environment variables - **Testing GraphQL mutations in Phase 14:** Only queries, mutations are Phase 15+ - **Skipping network connectivity verification:** Must validate HTTP access before assuming GraphQL works - **Using Docker socket proxy for Unraid API calls:** Different security boundary — Unraid API is host-level, not Docker daemon --- ## Don't Hand-Roll | Problem | Don't Build | Use Instead | Why | |---------|-------------|-------------|-----| | GraphQL client library | Custom HTTP client with schema parsing, error handling, type validation | n8n HTTP Request node with POST method | GraphQL-over-HTTP is standard, no client library needed | | API key rotation system | Automated key lifecycle management, expiry tracking | Manual rotation via `unraid-api apikey --delete` + recreate | Keys don't expire, rotation is infrequent | | Network discovery | Auto-detect Unraid host IP, scan network for GraphQL endpoints | User-configured `UNRAID_HOST` variable | Single-host environment, user knows Unraid IP | | Credential encryption at rest | Custom encryption wrapper for `.env.unraid-api` | n8n's built-in credential encryption (via `N8N_ENCRYPTION_KEY`) | n8n already handles credential security, file is for CLI/testing only | **Key insight:** Unraid GraphQL API is designed as a simple HTTP interface. Over-engineering the client side (schema validation, code generation, connection pooling) adds complexity without benefit for this use case. --- ## Common Pitfalls ### Pitfall 1: host.docker.internal Not Working on Linux Docker **What goes wrong:** `host.docker.internal` is a Docker Desktop feature. On native Linux Docker, it may not resolve, causing "connection refused" or "no such host" errors. **Why it happens:** Docker Engine on Linux doesn't automatically create the `host.docker.internal` DNS entry like Docker Desktop does on macOS/Windows. **How to avoid:** Add `--add-host=host.docker.internal:host-gateway` to container extra parameters. The `host-gateway` special value resolves to the host's bridge IP (typically `172.17.0.1`). **Warning signs:** - n8n HTTP Request node returns `getaddrinfo ENOTFOUND host.docker.internal` - `docker exec -it n8n curl http://host.docker.internal` fails with DNS error **Verification:** ```bash # From inside n8n container: docker exec -it n8n curl -I http://host.docker.internal/graphql # Should return HTTP 400 (GraphQL needs POST) or 200, NOT connection refused ``` **Source:** [Docker Bridge Network Guide](https://docs.docker.com/engine/network/drivers/bridge/), [Connecting to Host from Container](https://dev.to/iamrj846/connecting-to-the-host-machines-localhost-from-a-docker-container-a-practical-guide-nc4) ### Pitfall 2: HTTP vs HTTPS Confusion with Self-Signed Certs **What goes wrong:** User runs Unraid WebGUI on HTTPS with self-signed cert. HTTP Request node either fails with SSL verification error or connects to HTTP (port 80) when Unraid only listens on HTTPS (port 443). **Why it happens:** Unraid defaults to HTTP but many users enable HTTPS via Settings → Management Access → Use SSL/TLS. Self-signed certs fail Node.js default SSL validation. **How to avoid:** 1. Check Unraid WebGUI URL — if `https://tower.local`, use `https://` in `UNRAID_HOST` 2. In n8n HTTP Request node, set "Ignore SSL Issues" to `true` for self-signed certs 3. Document both HTTP and HTTPS configs in setup instructions **Warning signs:** - `UNABLE_TO_VERIFY_LEAF_SIGNATURE` error - `CERT_HAS_EXPIRED` error - Connection works on port 80 but not 443 (or vice versa) **Verification:** ```bash # Test which port Unraid listens on: curl -I http://tower.local/graphql # Port 80 curl -I https://tower.local/graphql # Port 443 ``` **Recommendation:** Default to HTTP in examples (simpler), document HTTPS config as optional enhancement. **Source:** Observed behavior in Unraid community forums, standard Node.js HTTPS client behavior ### Pitfall 3: Container ID Format Mismatch **What goes wrong:** Phase 15+ calls `updateContainer(id: "plex")` but Unraid expects `"docker:plex"` or numeric ID. Mutation returns "container not found" error despite container existing. **Why it happens:** GraphQL schema defines `PrefixedID` scalar but doesn't document exact format. Implementation may vary by Unraid version or container source (template vs. custom). **How to avoid:** 1. **Phase 14 MUST document actual ID format** observed in test query response 2. Store both `id` (GraphQL format) and `names` (Docker format) during testing 3. Phase 15 uses documented format from Phase 14, not assumptions **Warning signs:** - Test query returns IDs like `"123:456"` or `"docker:containername"` - Mutation fails with "invalid ID format" or "container not found" **Verification pattern:** ```graphql # Phase 14 test query: query { docker { containers { id names } } } # Response reveals format: { "data": { "docker": { "containers": [ { "id": "docker:plex", "names": ["plex"] } # Format discovered ] } } } ``` **Critical:** Do NOT assume format. Let test query reveal truth. **Source:** [Unraid API Schema](https://raw.githubusercontent.com/unraid/api/main/api/generated-schema.graphql) (defines PrefixedID as abstract), community client implementations show variation ### Pitfall 4: API Key Permissions Too Broad or Too Narrow **What goes wrong:** - **Too narrow:** API key created without `DOCKER:UPDATE_ANY`, Phase 15 mutations fail with "permission denied" - **Too broad:** API key has `ADMIN` role, excessive permissions violate least privilege **Why it happens:** Unraid API key creation supports both role-based (`--roles ADMIN`) and permission-based (`--permissions DOCKER:UPDATE_ANY`) access. Docs show both examples, easy to pick wrong one. **How to avoid:** 1. Use **permission-based scoping:** `--permissions "DOCKER:UPDATE_ANY"` 2. **Do NOT use `--roles ADMIN`** (grants full system access) 3. Phase 14 tests read-only query (permissions validation happens in Phase 15) **Warning signs:** - API key works for queries but fails for mutations - Unraid logs show "insufficient permissions" for Docker operations **Correct command:** ```bash unraid-api apikey --create \ --name "Docker Manager Bot" \ --permissions "DOCKER:UPDATE_ANY" \ --description "Container update status sync" ``` **Wrong command (don't use):** ```bash unraid-api apikey --create --roles ADMIN # Too broad! ``` **Source:** [Unraid API Key Management](https://docs.unraid.net/API/programmatic-api-key-management/), [Using Unraid API](https://docs.unraid.net/API/how-to-use-the-api/) ### Pitfall 5: Rate Limiting Surprises **What goes wrong:** Phase 16 batch sync calls `updateContainers` for 20+ containers, API returns 429 Too Many Requests or rate limit error. **Why it happens:** Unraid API documentation states "The API implements rate limiting to prevent abuse" but doesn't specify thresholds. Actual limits are unknown. **How to avoid:** 1. Phase 14/15 use single-container queries/mutations (well within any reasonable limit) 2. Phase 16 batch operations should use `updateContainers` (plural) mutation for efficiency, not N individual calls 3. If rate limiting occurs, implement exponential backoff (standard HTTP pattern) **Warning signs:** - HTTP 429 response - GraphQL error: `"message": "rate limit exceeded"` **Impact assessment:** LOW for this project (bot updates are infrequent, <10/min even in aggressive batch mode). Rate limiting unlikely to be hit in normal usage. **Mitigation:** Document in ARCHITECTURE.md, handle 429 gracefully (retry with backoff), consider it edge case not primary concern. **Source:** [Using Unraid API](https://docs.unraid.net/API/how-to-use-the-api/) (confirms rate limiting exists), specific thresholds not documented ### Pitfall 6: Unraid Version Compatibility (Connect Plugin Missing) **What goes wrong:** User runs Unraid 6.11 (pre-7.2), assumes GraphQL API is built-in, gets "404 Not Found" on `/graphql` endpoint. **Why it happens:** Native GraphQL API was added in Unraid 7.2. Earlier versions (6.9-7.1) require the Connect plugin to be installed. **How to avoid:** 1. Phase 14 setup documentation MUST include version check 2. If Unraid <7.2, document Connect plugin installation steps 3. Test query naturally fails if API unavailable, clear error message guides user to install plugin **Warning signs:** - HTTP 404 on `/graphql` endpoint - `curl http://tower.local/graphql` returns "Not Found" **Setup instructions pattern:** ```bash # Check Unraid version: cat /etc/unraid-version # SSH to Unraid # If <7.2: Install Connect plugin # Apps → Search "Unraid Connect" → Install # Settings → Management Access → API → Enable ``` **Verification:** ```bash # After setup, this should return HTTP 400 (GraphQL requires POST body): curl -I http://tower.local/graphql ``` **Source:** [Unraid API Documentation](https://docs.unraid.net/API/) (version availability), community forum discussions about Connect plugin --- ## Code Examples Verified patterns from official sources: ### Test Connectivity Query (Phase 14) ```javascript // n8n HTTP Request node configuration // Node name: "Test Unraid API" // Method: POST // URL: http://host.docker.internal/graphql // Authentication: Header Auth (credential: "Unraid API Key") // Body Content Type: JSON { "query": "query TestConnectivity { docker { containers { id names state isUpdateAvailable } } }" } // Expected response structure: { "data": { "docker": { "containers": [ { "id": "docker:plex", // Format may vary — document actual format "names": ["plex"], "state": "running", "isUpdateAvailable": false } // ... more containers ] } } } // Error handling pattern (n8n Code node after HTTP Request): const response = $input.item.json; if (response.errors) { // GraphQL returned errors const errorMsg = response.errors.map(e => e.message).join(', '); throw new Error(`Unraid API error: ${errorMsg}`); } if (!response.data || !response.data.docker) { // Unexpected response structure throw new Error('Invalid GraphQL response from Unraid API'); } // Success — pass container data to next node return { json: { success: true, containers: response.data.docker.containers, containerIdFormat: response.data.docker.containers[0]?.id // Document format } }; ``` **Source:** [n8n GraphQL Documentation](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.graphql/), Unraid API schema ### CLI Testing Pattern (Outside n8n) ```bash # Source credentials . .env.unraid-api # Test query via curl curl -X POST "${UNRAID_HOST}/graphql" \ -H "Content-Type: application/json" \ -H "x-api-key: ${UNRAID_API_KEY}" \ -d '{ "query": "query { docker { containers { id names state isUpdateAvailable } } }" }' \ | python3 -c " import sys, json data = json.load(sys.stdin) if 'errors' in data: print('ERROR:', data['errors']) sys.exit(1) containers = data['data']['docker']['containers'] print(f'Found {len(containers)} containers') for c in containers[:3]: # Show first 3 print(f\" {c['id']} - {c['names'][0]} ({c['state']})\") " ``` **Why CLI testing:** Validates API access outside n8n environment, useful for debugging network issues. ### Credential Storage Pattern ```bash # .env.unraid-api (gitignored) UNRAID_HOST=http://host.docker.internal UNRAID_API_KEY=1234567890abcdef # Load in bash scripts: . .env.unraid-api curl -H "x-api-key: ${UNRAID_API_KEY}" "${UNRAID_HOST}/graphql" ... # Reference in n8n: # Option 1: n8n credential system (recommended for workflows) # Option 2: Environment variables if n8n container has env file mounted ``` **Security note:** Never commit `.env.unraid-api` to git. Already in `.gitignore` (mirrors `.env.n8n-api` pattern). **Source:** [n8n Credentials Environment Variables](https://docs.n8n.io/hosting/configuration/environment-variables/credentials/), existing project `.env.n8n-api` pattern --- ## State of the Art | Old Approach | Current Approach | When Changed | Impact | |--------------|------------------|--------------|--------| | Direct file manipulation (`/var/lib/docker/unraid-update-status.json`) | GraphQL API `updateContainer` mutation | Unraid 6.9 (2020, Connect plugin) | Official interface, version-safe, handles internal state sync | | Session cookie auth | API key with x-api-key header | Unraid 6.9 API introduction | Stateless, no expiry handling, programmatic access | | Unraid CLI scripts (`/usr/local/emhttp/plugins/dynamix.docker.manager/`) | GraphQL API exposure of DockerService | Unraid 7.2 (2023, native API) | No PHP runtime needed, standard HTTP interface | **Deprecated/outdated:** - **Direct `/var/lib/docker/unraid-update-status.json` writes:** File format undocumented, no UI refresh trigger, brittle - **Unraid API-RE (community plugin):** Replaced by official Unraid API/Connect plugin, no longer maintained - **WebGUI session scraping:** Fragile, breaks on Unraid updates, security risk **Current best practice (2026):** Use official GraphQL API via HTTP Request with API key authentication. Mirrors how Unraid's own WebGUI communicates with backend. --- ## Open Questions 1. **Exact container ID format in GraphQL responses** - What we know: Schema defines `PrefixedID` scalar, format is `:` - What's unclear: Whether it's `"docker:containername"`, numeric like `"123:456"`, or varies by source - Recommendation: Phase 14 test query MUST document actual format observed, used by Phase 15+ 2. **HTTP vs HTTPS default on user's Unraid** - What we know: Unraid defaults to HTTP but many users enable HTTPS - What's unclear: How many users have self-signed certs vs. valid certs vs. HTTP-only - Recommendation: Default to HTTP in examples, document HTTPS + "Ignore SSL" as configuration option 3. **Rate limiting thresholds** - What we know: API implements rate limiting (confirmed in docs) - What's unclear: Actual request/minute limits, burst allowances - Recommendation: Assume reasonable limits (100+/min), handle 429 gracefully, unlikely to hit in practice 4. **Unraid version distribution (6.x vs 7.x)** - What we know: 7.2+ has native API, 6.9-7.1 needs Connect plugin, <6.9 unsupported - What's unclear: User's actual Unraid version - Recommendation: Phase 14 setup instructions check version, guide plugin install if needed --- ## Sources ### Primary (HIGH confidence) - [Unraid GraphQL Schema](https://raw.githubusercontent.com/unraid/api/main/api/generated-schema.graphql) — Container queries, mutation signatures, PrefixedID definition - [Using the Unraid API](https://docs.unraid.net/API/how-to-use-the-api/) — Endpoint URL, authentication header format, rate limiting acknowledgment - [Unraid API Key Management](https://docs.unraid.net/API/programmatic-api-key-management/) — API key creation, permission scoping - [n8n GraphQL Node Documentation](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.graphql/) — HTTP Request node GraphQL patterns - [Docker Bridge Network](https://docs.docker.com/engine/network/drivers/bridge/) — host.docker.internal behavior, bridge IP access ### Secondary (MEDIUM confidence) - [n8n Security Best Practices](https://docs.n8n.io/hosting/configuration/environment-variables/credentials/) — Credential storage, encryption - [Connecting to Host from Docker Container](https://dev.to/iamrj846/connecting-to-the-host-machines-localhost-from-a-docker-container-a-practical-guide-nc4) — host.docker.internal Linux workarounds - Project `.planning/research/STACK.md` — Prior v1.3 research on Unraid API (same domain, validates findings) - Project `.planning/research/SUMMARY.md` — Architecture patterns for this project ### Tertiary (LOW confidence) - Community forum posts on Unraid API authentication — Anecdotal troubleshooting examples - GitHub client implementations ([unraid-mcp](https://github.com/jmagar/unraid-mcp), [ha-unraid](https://github.com/domalab/unraid-api-client)) — Reference patterns, not official docs --- ## Metadata **Confidence breakdown:** - Standard stack: HIGH — GraphQL API is official, HTTP Request node is n8n built-in, well-documented - Architecture: HIGH — Network patterns are standard Docker, credential storage mirrors existing project pattern - Pitfalls: MEDIUM-HIGH — Most are standard HTTP/Docker issues, container ID format needs empirical validation **Research date:** 2026-02-08 **Valid until:** 60 days (Unraid API is stable, GraphQL schema changes infrequent) **Critical dependencies for planning:** - Phase 14 MUST validate container ID format via test query (open question #1) - Phase 14 MUST document HTTP vs HTTPS configuration for user's environment (open question #2) - Setup instructions MUST include Unraid version check and Connect plugin guidance (open question #4) **Ready for planning:** YES — Sufficient information to create tasks for API key creation, network configuration, test query implementation, and container ID format documentation.