App API reference
A native app has one Go backend. The UI calls it with named methods such as greeting.get. This page lists the call format, the permission levels, the methods that the templates include, the Go API for adding routes, and the client helpers in the generated UI.
Call format
Section titled “Call format”The UI calls ctx.call(method, params). DSM passes ctx to your UI when the window opens. ctx.call sends a JSON POST to the app’s backend with the user’s DSM session.
| Part | Value |
|---|---|
| Request body | {"method": "app.status", "params": {}}. Unknown fields are refused. |
| Success | {"success": true, "data": …}. ctx.call resolves with data. |
| Error | {"success": false, "error": {"code": "…", "message": "…"}}. ctx.call rejects with {code, message}. |
| HTTP status | Always 200 for API errors, so DSM does not replace the body. |
| Origin | Same origin only. The Content-Type must be application/json. |
Codes are listed in Error codes. File upload and download do not use ctx.call. They use an HTTP transfer with the method in the query string (?method=nas.files.upload and ?method=nas.files.download). The generated file helpers do this for you.
Permissions
Section titled “Permissions”Every method has one permission level. The level is set outside the handler, in backend/internal/api/permissions.json.
| Level | Who can call |
|---|---|
administrators | Members of the DSM administrators group. This is the default. |
authenticated | Any signed-in DSM user who passes the entry check. |
public | Anyone. No sign-in. Only the identity.* methods use it. |
A method that is not listed gets administrators. A non-administrator who calls an unlisted method gets admin_required. A method that is not registered gets unknown_method for every signed-in caller.
{ "schema": 1, "default": "administrators", "methods": { "app.status": "authenticated", "reports.list": "authenticated" }}| Rule | Value |
|---|---|
| File shape | Exactly schema, default and methods. default is administrators. |
| Method names | Lowercase area.action form, such as reports.list. Pattern ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$, at most 128 characters. |
| Size | At most 1024 methods and 64 KiB. Duplicate keys are refused. |
| Registered methods | Every listed method must have a handler. |
| Methods of capabilities | The permissions of nas.*, bg.*, identity.*, portal.*, executor.*, root.run and delegate.* are fixed. You cannot change them in permissions.json. |
check and build validate the file. Errors use STANDALONE_API_PERMISSIONS_INVALID. See Error codes.
Entry gate
Section titled “Entry gate”The entry field of dsm-package.yaml limits who can open the app at all.
entry | Effect |
|---|---|
| Omitted | No extra check. Any user who can open the window can call authenticated methods. |
admin-only | Only administrators. Others get admin_required. |
app-privilege | Only users that DSM Application Privileges allow. Others get app_privilege_denied. It needs at least one method that is not administrators. Files or jobs alone satisfy this. |
Administrators always pass the gate. public methods skip it. See Manifest reference.
Built-in methods
Section titled “Built-in methods”Methods exist only when the matching capability is on. Add a capability with synopkgland add <name> or init --with <name>.
Base template
Section titled “Base template”| Method | Permission | Purpose |
|---|---|---|
app.status | administrators | Package name, version, caller (user, admin), process, platform and time. Adds shared_folders when the app has a Shared Folder. |
greeting.get | administrators | Reads the example greeting. Returns {text}. |
greeting.set | administrators | Writes the example greeting. {text} must be 1 to 200 characters. |
These three are examples. Replace them with your own methods. init --with delegate also lists app.status as authenticated.
Files (files)
Section titled “Files (files)”| Method | Permission | Params | Purpose |
|---|---|---|---|
nas.session.status | authenticated | none | Returns {mode: "filestation-session"} when file access is on. |
nas.files.shares | authenticated | offset | Lists Shared Folders the user can see. |
nas.files.list | authenticated | path, offset, limit (at most 100), sortBy (name, size, type, mtime), sortDirection (ASC, DESC) | Lists a folder. |
nas.files.info | authenticated | path | Returns one file or folder. |
nas.files.create_folder | authenticated | parent, name | Creates a folder. Returns {created: true}. |
nas.files.upload | authenticated | Query parent, name. Body is the file. | Uploads a file. It never overwrites and never creates the parent. Needs Content-Length, at most 16 GiB. |
nas.files.download | authenticated | Query path | Downloads a file as an attachment. |
Every call runs with the signed-in user’s own DSM permissions. Paths are absolute, at most 4096 characters, and contain no \, ,, " or control characters. A list page has at most 100 entries.
Background jobs (jobs)
Section titled “Background jobs (jobs)”| Method | Permission | Purpose |
|---|---|---|
bg.status | authenticated | Owner, limits, and the number of queued and running jobs. |
bg.browse | authenticated | Browse the caller’s folders to pick a job input. Works without files. |
bg.jobs.submit | authenticated | Queue a job. Params key, folder, relative. Returns {job, duplicate}. |
bg.jobs.list | authenticated | List the caller’s jobs, newest first. |
bg.jobs.get | authenticated | One job by id. |
bg.jobs.cancel | authenticated | Cancel a job by id. |
bg.jobs.result | authenticated | The result of a finished job, as JSON. not_ready before it finishes. |
bg.jobs.purge | administrators | Purge the job data of a user (owner: {user, uid}). |
bg.schedules.list | authenticated | List schedules. |
bg.schedules.create | authenticated | Repeat a job. Params key, jobId, intervalSeconds (3600, 86400 or 604800), consent: true. |
bg.schedules.disable | authenticated | Stop a schedule by id. |
bg.schedules.delete | authenticated | Delete a schedule by id. |
bg.schedules.enable | administrators | Start a schedule again. Params id, consent: true. |
A job key matches ^[A-Za-z0-9._-]{1,64}$. Job states are queued, running, succeeded, failed, canceled and interrupted. A failed job has an errorCode; see Error codes.
Login (login)
Section titled “Login (login)”All identity.* methods are public. They need HTTPS and a same-origin JSON POST.
| Method | Params | Purpose |
|---|---|---|
identity.status | none | Returns {enabled: true}. |
identity.session | bootstrap (optional) | Returns {user, uid, expiresAt} for the app session. With bootstrap: true and no app session, it starts one from the DSM sign-in. |
identity.login | account, password, otp (optional) | Signs the user in with their DSM account and starts an app session. |
identity.logout | none | Ends the app session. Returns {loggedOut: true}. |
Application Portal (portal)
Section titled “Application Portal (portal)”| Method | Permission | Purpose |
|---|---|---|
portal.status | authenticated | Reads the saved portal alias and whether it is active. |
Administrators set or clear the alias from the UI through ctx.portal. See Application Portal guide.
Administrator operations (rootOps, delegate)
Section titled “Administrator operations (rootOps, delegate)”| Method | Permission | Params | Purpose |
|---|---|---|---|
executor.status | authenticated | none | State of the approved helper: digests, version, ready, disabled, and tasks, the executor’s own readback of its approval tasks, {state, checked_at}: checking until the first readback after the executor started ends, then ready, stale (a ready readback older than 15 minutes) or failed. The executor reads back at start, every 10 minutes after a pass and every minute after a failure. While the executor is paused it answers {ready: false, state: "paused", reason: "executor_not_listening", next}. |
executor.control | administrators | action: start, stop or purge | Starts, stops or purges the helper. |
root.run | administrators | name | Runs root/<name>.sh as root and returns {output}. No arguments. |
Delegate projects get executor.* too. A failure of root.run, or of executor.status other than a paused executor, reaches the UI as internal. See Administrator operations guide.
Delegate (delegate)
Section titled “Delegate (delegate)”The default delegate setting (lifetime: until-revoked) uses these methods.
| Method | Permission | Params | Purpose |
|---|---|---|---|
delegate.status | authenticated | none | State (ready, disabled, needs-admin-approval, blocked, needs-scope-confirmation), lifetime, destinations and scope. |
delegate.destination | authenticated | key | State of one destination: active, missing, reselection-required, needs-scope-confirmation or disabled. |
delegate.select_destination | authenticated | key, path | Lets the user pick a folder for a destination. Checks write permission, then saves the grant. |
delegate.move_from_staging | authenticated | delegation_id, job_id, staging, name, size, optional conflict | Moves a finished file from staging into the chosen folder. name may contain folders, such as Album/CD1/01.flac; conflict is fail (default), overwrite or rename. |
delegate.operations.get | authenticated | delegation_id, job_id | State of a move. |
delegate.operations.cancel | authenticated | delegation_id, job_id | Cancels a move. |
delegate.operations.list | authenticated | none (send {}) | Lists moves. |
delegate.example.export | authenticated | key, job_id | Example export used by the save-later panel. |
delegate.disable, delegate.enable | administrators | none | Turn the integration off or on. Both end all grants. |
delegate.audit | authenticated | none | Events of the package. Administrators see all, others see their own. At most 256. |
With delegate and no lifetime, the time-limited methods apply instead: delegate.create (job_id, ops, path, ttl_seconds from 1 to 3600), delegate.list, delegate.revoke, delegate.jobs.submit, delegate.jobs.list and delegate.jobs.cancel. All are authenticated. The delegate.* methods need a DSM session cookie and token, otherwise they return session_required. See Delegate guide.
Executor protocol
Section titled “Executor protocol”The app backend reaches the executor with these ops of executor protocol v1; this table is generated from the op schemas in the SDK. A parameter ending in ? is optional. Ops that need a DSM session refuse without one with AUTH_DENIED. See Executor protocol codes.
| Op | DSM session | Parameters |
|---|---|---|
delegate.audit | Required | — |
delegate.create | Required | job_id?, ops, scope, path, ttl_seconds?, destination?, lifetime? |
delegate.disable | Required | — |
delegate.enable | Required | — |
delegate.list | Required | — |
delegate.revoke | Optional | delegation_id, job_id? |
files.cancelOperation | Optional | delegation_id, job_id |
files.checkPermission | Optional | delegation_id, job_id, path |
files.copy | Optional | delegation_id, job_id, path, dest_dir, conflict? |
files.createFolder | Optional | delegation_id, job_id, path, name, parents? |
files.delete | Optional | delegation_id, job_id, path, recursive?, permanent? |
files.info | Optional | delegation_id, job_id, path |
files.list | Optional | delegation_id, job_id, path |
files.move | Optional | delegation_id, job_id, path, dest_dir, conflict? |
files.moveFromStaging | Optional | delegation_id, job_id, staging, name, size, conflict? |
files.operationStatus | Optional | delegation_id, job_id |
files.read | Optional | delegation_id, job_id, path, max_bytes? |
files.rename | Optional | delegation_id, job_id, path, name |
files.taskCancel | Optional | delegation_id, job_id, task_id |
files.taskStatus | Optional | delegation_id, job_id, task_id |
files.write | Optional | delegation_id, job_id, staging, dest_dir, name, conflict? |
portal.register | Required | — |
portal.unregister | Required | — |
root.jobs.get | Required | job_id |
root.jobs.list | Required | — |
root.jobs.stop | Required | job_id |
root.run | Required | name, params? |
root.start | Required | name, params? |
status | Optional | — |
Add a route in Go
Section titled “Add a route in Go”Routes live in backend/internal/app/routes.go. Register a handler there, then give the method a permission in permissions.json.
// routes.go: add to the list in Routesapi.HandleRequest("reports.list", cfg.listReports),package app
import ( "context" "encoding/json" "net/http"
"github.com/synopackageland/syno-pkg-land-sdk/api" "github.com/synopackageland/syno-pkg-land-sdk/filestation")
func (cfg Config) listReports(ctx context.Context, caller api.Caller, r *http.Request, params json.RawMessage) (any, error) { var in struct { Folder string `json:"folder"` } if err := decodeParams(params, &in); err != nil { return nil, err } if in.Folder == "" { return nil, api.BadRequest("folder is required") } return cfg.SessionFiles.List(ctx, caller, r, in.Folder, filestation.ListOptions{Limit: 50})}{ "schema": 1, "default": "administrators", "methods": { "reports.list": "authenticated" }}Registration functions
Section titled “Registration functions”| Function | Handler signature | Use it for |
|---|---|---|
api.Handle(method, h) | func(ctx, caller api.Caller, params json.RawMessage) (any, error) | Most routes. |
api.HandleRequest(method, h) | Adds the *http.Request after ctx. | Routes that call File Station or the executor with the user’s session. |
api.HandleResponse(method, h) | Adds http.ResponseWriter and *http.Request. Returns error. | Routes that set cookies or write the response. |
api.HandleStream(method, h) | func(ctx, caller, w, r) error | Upload and download. The method comes from the query string and the body is not JSON. |
The return value of a handler becomes data. The permission of a route never comes from the function you use. It comes from permissions.json.
Caller and errors
Section titled “Caller and errors”| Item | Use |
|---|---|
caller.User() | The DSM user name. |
caller.Admin() | true for DSM administrators. |
caller.Authenticated() | true when a user is signed in. |
api.RequireAdmin(caller) | Returns admin_required when the caller is not an administrator. Use it to reserve part of a shared route. |
api.BadRequest(msg) | Returns bad_request with your message. |
&api.Error{Code: "…", Message: "…"} | Returns your own code. The UI sees it. |
Any other error | Logged on the NAS and returned to the UI as internal. |
Derive owners from caller.User(). Never trust a user name sent in params.
Capabilities from handlers
Section titled “Capabilities from handlers”| You need | Use |
|---|---|
| Files with the user’s permissions | cfg.SessionFiles.List, Info, CreateFolder, Upload and Download. Each takes ctx, caller and the request r. Needs files. |
| One interface for a live request and a delegate job | filestation.RequestContext{Caller, Request, Service}.Files(), with List, Info, CreateFolder, CheckPermission and Write. Needs delegate. |
| File operations of a delegate job | delegate.JobContext.Files() adds CreateFolderAll, Rename, Read, and the task operations Copy, Move, Delete, TaskStatus and CancelTask. Each needs its operation in the delegate operations. See Delegate guide. |
| Background jobs | Call the bg.* methods from the UI. The worker is fixed and computes a SHA-256. See Background jobs guide. |
| Save a file later, without a user session | Delegate. Call service.Destination(ctx, caller, req, "save") while the user is present, keep Destination.ID, then call service.MoveFromStaging(ctx, MoveParams{DelegationID, JobID, Staging, Name, Size}). |
| A root operation | The administrator’s UI calls root.run with the script name. Put the script in root/<name>.sh. No generated UI calls it. Call ctx.call('root.run', {name}) yourself. |
UI client
Section titled “UI client”ctx is the context the DSM window passes to your UI. The other helpers come from @synopackageland/sdk (headless, no Vue) and @synopackageland/sdk/vue (Vue components and composables; import @synopackageland/sdk/vue/style.css once). Its version must equal the SDK version in backend/go.mod; check reports SDK_VERSION_MISMATCH otherwise. @synopackageland/ui holds the visual components only. It has no backend client.
| Helper | Where | Purpose |
|---|---|---|
ctx.call(method, params) | ctx (all UIs) | Calls a backend method. Resolves with data, rejects with {code, message}. |
ctx.locale | ctx | The DSM language, such as en or zh-TW. |
ctx.reopen(fn) | ctx | Runs fn when the window is shown again. Returns a function that removes it. |
ctx.host | ctx | Window and file manager actions: setTitle, openApp, openURL, openFileManager, revealFile, subscribe. |
ctx.user | ctx | {name, isAdmin} from DSM. Use it only to hide controls. The backend decides access. |
ctx.files | ctx | {endpoint, token} for file transfers. Present when the app has files. |
ctx.portal | ctx | status(), apply(alias), disable(), openControlPanel(), mountPanel(el). Present when the app declares portal. |
ctx.executor | ctx | Administrator approval: status(), authorize(password, scope?), control(action). Present with rootOps or delegate. |
ctx.t(key, params) | ctx (DSM window) | Translated text from i18n/messages. |
createNasProvider(ctx) | @synopackageland/sdk | File provider for the picker: shares, list, info, createFolder, upload, download. |
createSession(ctx) | @synopackageland/sdk/vue | Login session state and calls: login, bootstrap, logout, call. |
createBackgroundSaveFacade(ctx) | @synopackageland/sdk | Wraps the delegate.* methods for the save-later panel. |
FilesPage, FileBrowser, NasPicker | @synopackageland/sdk/vue | Vue components for browsing and choosing files and folders. |
LoginPage | @synopackageland/sdk/vue | Vue sign-in form. It needs https:. |
BackgroundPanel | @synopackageland/sdk/vue | Vue panel for jobs, built on bg.browse. |
ExecutorSettings | @synopackageland/sdk/vue | Vue panel where an administrator approves background access. |
ExecutorApproval | @synopackageland/sdk/vue | Holds the app’s pages until the executor is approved. With preload, the pages mount hidden while the first status is read, so their own reads run alongside it; a ready status shows that same instance, any other discards it. |
useExecutorPreload() | @synopackageland/sdk/vue | true while the pages are preloaded. When the status is not ready it turns false before the pages unmount, so false does not mean the pages show: open a teleported dialog or a toast from watchPostEffect. AuthorizationGate waits by itself. |
PortalSettings | @synopackageland/sdk/vue | Vue panel for the portal alias. |
// A Vue component with ctx as a propconst list = await ctx.call<{ files: { name: string; isdir: boolean }[] }>('reports.list', { folder: '/volume1/reports',})try { await ctx.call('greeting.set', { text: 'Hello' })} catch (e) { const code = (e as { code?: string }).code // for example "admin_required"}Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Request body | 1 MiB |
| Time for one request | 20 seconds |
Methods in permissions.json | 1024, file at most 64 KiB |
| List page | 100 entries |
| Upload size | 16 GiB |
| App login session | 15 minutes at most |
| Sign-in attempts | 5 per minute per account, 20 per minute per address |