-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
Base path: /api/v1. All responses are JSON unless noted; errors are {"error":"message"} with a meaningful status. Paths are absolute, /-separated, relative to the data root: /documents/Invoices/2026-01.pdf. The first component is a share.
POST /api/v1/auth/login
Content-Type: application/json
{"apiKey":"<unraid api key>","username":"sdimambro","password":"…"}username/password are optional unless the gateway runs with USER_AUTH=required; with USER_AUTH=off they are ignored.
{"token":"…","expiresAt":"2026-09-09T02:00:00Z","identity":{"name":"unraid gateway","roles":["VIEWER"]},
"readOnly":false,"version":"v0.3.0","userAuth":"optional",
"user":"sdimambro","shares":{"documents":"rw","media":"ro"}}user and shares are present only when a username was given. shares maps every share the user may see to rw or ro, derived from Unraid's share security. Shares the user may not read are hidden: listing them returns 404; writing into ro shares returns 403 this share is read-only for your user. Failed user logins count towards the per-IP lockout.
Then send Authorization: Bearer <token>. Alternatively send x-api-key: <unraid api key> on each request; validations are cached for five minutes. 401 means the token expired (log in again), 429 means the client IP is locked out.
| Method | Path | |
|---|---|---|
POST |
/auth/logout |
invalidates the token |
GET |
/auth/session |
identity and readOnly
|
GET |
/info |
version, readOnly, identity, feature list |
GET |
/healthz (no auth) |
{"status":"ok","version":"…"} |
Since 0.5 every entry also carries id (stable across renames, moves and restarts) and parentId (root for shares). GET /fs/item?id=<id> returns the current entry for an id; GET /fs/changes?seq=<n> is the id-based journal (see Change Feed).
{"name":"IMG_0001.HEIC","path":"/media/Photos/IMG_0001.HEIC","type":"file","size":2345678,
"mtime":"2026-09-08T14:03:11.120Z","etag":"\"23cace-1856f2c0a3b1c2d0\"","mode":"-rw-rw-r--"}type is file or dir. etag is derived from size and mtime; it is also sent as the ETag header on stat, content and PUT.
| Method | Path | Notes |
|---|---|---|
GET |
/fs/list?path=&hidden=1 |
Directory listing, directories first, case-insensitive order. Dot-files hidden unless hidden=1. Response has the directory etag/mtime and entries. |
GET |
/fs/stat?path= |
One entry. |
GET / HEAD
|
/fs/content?path=&download=1 |
Streams the file. Supports Range, If-None-Match, If-Range, If-Modified-Since. download=1 adds Content-Disposition: attachment. |
PUT |
/fs/content?path=&overwrite=false |
Whole-file upload; body is the content. Written to a temp sibling then renamed atomically. 201 created / 200 replaced. Headers: If-Match: <etag> for optimistic locking (412 on mismatch), X-Mtime: <RFC 3339> to preserve the client's modification time. 409 if the target is a directory or exists with overwrite=false. |
POST |
/fs/mkdir |
{"path":"/share/a/b","parents":true} → 201 entry. |
POST |
/fs/move |
{"from":"/a/x","to":"/b/x","overwrite":false} → 200 entry. Falls back to copy+delete across devices. |
POST |
/fs/copy |
same body → 201 entry. Recursive. |
POST |
/fs/delete |
{"path":"/a/x","recursive":false} → 204. Non-empty directory without recursive → 409. |
GET |
/fs/changes?path=&since=&after=&cursor=&limit= |
Change feed, see Change feed and sync. |
| Code | Meaning |
|---|---|
400 |
invalid path, destination inside source, bad parameters |
403 |
read-only gateway or share, share root mutation (shares cannot be renamed/moved/deleted, nothing can be created at root), permission denied |
404 |
not found; also share not found when the first path component is not a mounted share |
409 |
exists / not empty / directory conflicts / upload offset mismatch |
412 |
If-Match mismatch |
507 |
no space left on the share |
For large files and unreliable links. The temp file lives next to the destination, so commit is an atomic rename.
POST /fs/uploads {"path":"/media/clip.mov","size":734003200,"overwrite":false}
→ 201 {"id":"…","path":"…","offset":0,"expiresAt":"…"} (header Upload-Offset: 0)
PATCH /fs/uploads/{id} Upload-Offset: <current offset> body: bytes
→ 204, Upload-Offset: <new offset>
HEAD /fs/uploads/{id} → Upload-Offset: <current offset> (resume after a drop)
POST /fs/uploads/{id}/commit X-Mtime: <RFC 3339, optional> → 201 entry
DELETE /fs/uploads/{id} → 204 (abort)A PATCH with the wrong Upload-Offset returns 409 and the correct offset in the header. Two concurrent PATCHes on the same upload return 409 upload busy. If size was given, commit refuses (400) until all bytes arrived. Sessions expire after UPLOAD_TTL.
POST /api/v1/graphql
Authorization: Bearer <token>
{"query":"{ array { state } shares { name free used } }"}Forwarded verbatim to UNRAID_URL/graphql with the session's x-api-key; the response is returned unchanged. What you can query depends on the key's Unraid role. Introspection is disabled by Unraid; the fields below were validated on Unraid 7.3 / unraid-api 4.37 with a VIEWER key:
{ array { state capacity { kilobytes { free used total } } disks { name status temp fsSize fsFree } parityCheckStatus { status progress running } }
shares { name free used comment }
docker { containers { id names state image autoStart isUpdateAvailable webUiUrl iconUrl } }
info { os { hostname uptime release } cpu { brand cores threads } }
notifications { overview { unread { total warning alert } } }
metrics { cpu { percentTotal } memory { percentTotal used total } } }GW=https://gw.example.com
TOKEN=$(curl -s -X POST $GW/api/v1/auth/login -H 'Content-Type: application/json' \
-d '{"apiKey":"'"$KEY"'"}' | jq -r .token)
H="Authorization: Bearer $TOKEN"
curl -s "$GW/api/v1/fs/list?path=/documents" -H "$H" | jq .
curl -s -T ./file.pdf "$GW/api/v1/fs/content?path=/documents/file.pdf" -H "$H"
curl -s "$GW/api/v1/fs/content?path=/documents/file.pdf" -H "$H" -o file.pdf
curl -s -X POST $GW/api/v1/fs/mkdir -H "$H" -d '{"path":"/documents/New"}'
curl -s "$GW/api/v1/fs/changes?path=/documents&since=0" -H "$H" | jq '{cursor,truncated,next,dirs:(.dirs|length),files:(.files|length)}'| Method | Path | Notes |
|---|---|---|
POST |
/api/v1/fs/ticket |
Body {"path": "/share/file", "ttl": "8h"} (ttl optional, max 24h). Needs read access. Returns {"ticket", "url": "/media/<ticket>", "path", "expiresAt"}. |
GET/HEAD
|
/media/<ticket> |
Public: the ticket is the credential. Streams the file with Accept-Ranges, ETag, Content-Type by extension. 403 for a bad or expired ticket. |
Tickets are HMAC-SHA256 signed tokens (base64url(payload).base64url(signature)) bound to one path and an expiry, with a random per-process secret: a gateway restart invalidates them. Use them for players that cannot set headers (libmpv, VLC, <video>).
| Method | Path | Notes |
|---|---|---|
GET |
/api/v1/activity |
`{now, scope: "all" |
?format=text renders the same as plain text (used by gw activity); requests from the container's loopback address need no token. Clients identify themselves with X-Unraid-Drive-Client (free text, shown as the device); without it the User-Agent is shown.