25 KiB
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://<unraid-host>/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>
User Constraints (from CONTEXT.md)
Locked Decisions
Credential management
- Dual storage:
.env.unraid-apifile (for CLI/deploy scripts) + n8n credential (for workflow nodes) .env.unraid-apicontains bothUNRAID_API_KEYandUNRAID_HOST(mirrors.env.n8n-apipattern)- 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_HOSTenv 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 </user_constraints>
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:
# 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):
# .env.unraid-api (gitignored)
UNRAID_HOST=http://host.docker.internal
UNRAID_API_KEY=your-api-key-here
n8n credential configuration:
- Credentials → Add Credential → Header Auth
- Name:
Unraid API Key - Header Name:
x-api-key - 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)
// 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
// 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:
query TestConnectivity {
docker {
containers {
id
names
state
isUpdateAvailable
}
}
}
What this validates:
- Network connectivity (can reach
/graphqlendpoint) - Authentication (API key accepted)
- Permissions (can read Docker data)
- Container ID format (see actual ID structure in response)
n8n HTTP Request node body:
{
"query": "query { docker { containers { id names state isUpdateAvailable } } }"
}
Container ID Format Documentation Pattern
Research finding: Unraid uses PrefixedID scalar type. Format is <server_id>:<resource_id> 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.internalfails with DNS error
Verification:
# 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, Connecting to Host from Container
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:
- Check Unraid WebGUI URL — if
https://tower.local, usehttps://inUNRAID_HOST - In n8n HTTP Request node, set "Ignore SSL Issues" to
truefor self-signed certs - Document both HTTP and HTTPS configs in setup instructions
Warning signs:
UNABLE_TO_VERIFY_LEAF_SIGNATUREerrorCERT_HAS_EXPIREDerror- Connection works on port 80 but not 443 (or vice versa)
Verification:
# 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:
- Phase 14 MUST document actual ID format observed in test query response
- Store both
id(GraphQL format) andnames(Docker format) during testing - 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:
# 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 (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
ADMINrole, 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:
- Use permission-based scoping:
--permissions "DOCKER:UPDATE_ANY" - Do NOT use
--roles ADMIN(grants full system access) - 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:
unraid-api apikey --create \
--name "Docker Manager Bot" \
--permissions "DOCKER:UPDATE_ANY" \
--description "Container update status sync"
Wrong command (don't use):
unraid-api apikey --create --roles ADMIN # Too broad!
Source: Unraid API Key Management, Using Unraid 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:
- Phase 14/15 use single-container queries/mutations (well within any reasonable limit)
- Phase 16 batch operations should use
updateContainers(plural) mutation for efficiency, not N individual calls - 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 (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:
- Phase 14 setup documentation MUST include version check
- If Unraid <7.2, document Connect plugin installation steps
- Test query naturally fails if API unavailable, clear error message guides user to install plugin
Warning signs:
- HTTP 404 on
/graphqlendpoint curl http://tower.local/graphqlreturns "Not Found"
Setup instructions pattern:
# 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:
# After setup, this should return HTTP 400 (GraphQL requires POST body):
curl -I http://tower.local/graphql
Source: Unraid API Documentation (version availability), community forum discussions about Connect plugin
Code Examples
Verified patterns from official sources:
Test Connectivity Query (Phase 14)
// 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, Unraid API schema
CLI Testing Pattern (Outside n8n)
# 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
# .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, 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.jsonwrites: 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
-
Exact container ID format in GraphQL responses
- What we know: Schema defines
PrefixedIDscalar, format is<server>:<resource> - 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+
- What we know: Schema defines
-
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
-
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
-
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 — Container queries, mutation signatures, PrefixedID definition
- Using the Unraid API — Endpoint URL, authentication header format, rate limiting acknowledgment
- Unraid API Key Management — API key creation, permission scoping
- n8n GraphQL Node Documentation — HTTP Request node GraphQL patterns
- Docker Bridge Network — host.docker.internal behavior, bridge IP access
Secondary (MEDIUM confidence)
- n8n Security Best Practices — Credential storage, encryption
- Connecting to Host from Docker Container — 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, ha-unraid) — 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.