Skip to content
Syno Package Land
Language: English

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.

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.

PartValue
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 statusAlways 200 for API errors, so DSM does not replace the body.
OriginSame 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.

Every method has one permission level. The level is set outside the handler, in backend/internal/api/permissions.json.

LevelWho can call
administratorsMembers of the DSM administrators group. This is the default.
authenticatedAny signed-in DSM user who passes the entry check.
publicAnyone. 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"
}
}
RuleValue
File shapeExactly schema, default and methods. default is administrators.
Method namesLowercase area.action form, such as reports.list. Pattern ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$, at most 128 characters.
SizeAt most 1024 methods and 64 KiB. Duplicate keys are refused.
Registered methodsEvery listed method must have a handler.
Methods of capabilitiesThe 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.

The entry field of dsm-package.yaml limits who can open the app at all.

entryEffect
OmittedNo extra check. Any user who can open the window can call authenticated methods.
admin-onlyOnly administrators. Others get admin_required.
app-privilegeOnly 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.

Methods exist only when the matching capability is on. Add a capability with synopkgland add <name> or init --with <name>.

MethodPermissionPurpose
app.statusadministratorsPackage name, version, caller (user, admin), process, platform and time. Adds shared_folders when the app has a Shared Folder.
greeting.getadministratorsReads the example greeting. Returns {text}.
greeting.setadministratorsWrites 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.

MethodPermissionParamsPurpose
nas.session.statusauthenticatednoneReturns {mode: "filestation-session"} when file access is on.
nas.files.sharesauthenticatedoffsetLists Shared Folders the user can see.
nas.files.listauthenticatedpath, offset, limit (at most 100), sortBy (name, size, type, mtime), sortDirection (ASC, DESC)Lists a folder.
nas.files.infoauthenticatedpathReturns one file or folder.
nas.files.create_folderauthenticatedparent, nameCreates a folder. Returns {created: true}.
nas.files.uploadauthenticatedQuery 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.downloadauthenticatedQuery pathDownloads 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.

MethodPermissionPurpose
bg.statusauthenticatedOwner, limits, and the number of queued and running jobs.
bg.browseauthenticatedBrowse the caller’s folders to pick a job input. Works without files.
bg.jobs.submitauthenticatedQueue a job. Params key, folder, relative. Returns {job, duplicate}.
bg.jobs.listauthenticatedList the caller’s jobs, newest first.
bg.jobs.getauthenticatedOne job by id.
bg.jobs.cancelauthenticatedCancel a job by id.
bg.jobs.resultauthenticatedThe result of a finished job, as JSON. not_ready before it finishes.
bg.jobs.purgeadministratorsPurge the job data of a user (owner: {user, uid}).
bg.schedules.listauthenticatedList schedules.
bg.schedules.createauthenticatedRepeat a job. Params key, jobId, intervalSeconds (3600, 86400 or 604800), consent: true.
bg.schedules.disableauthenticatedStop a schedule by id.
bg.schedules.deleteauthenticatedDelete a schedule by id.
bg.schedules.enableadministratorsStart 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.

All identity.* methods are public. They need HTTPS and a same-origin JSON POST.

MethodParamsPurpose
identity.statusnoneReturns {enabled: true}.
identity.sessionbootstrap (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.loginaccount, password, otp (optional)Signs the user in with their DSM account and starts an app session.
identity.logoutnoneEnds the app session. Returns {loggedOut: true}.
MethodPermissionPurpose
portal.statusauthenticatedReads 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)”
MethodPermissionParamsPurpose
executor.statusauthenticatednoneState 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.controladministratorsaction: start, stop or purgeStarts, stops or purges the helper.
root.runadministratorsnameRuns 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.

The default delegate setting (lifetime: until-revoked) uses these methods.

MethodPermissionParamsPurpose
delegate.statusauthenticatednoneState (ready, disabled, needs-admin-approval, blocked, needs-scope-confirmation), lifetime, destinations and scope.
delegate.destinationauthenticatedkeyState of one destination: active, missing, reselection-required, needs-scope-confirmation or disabled.
delegate.select_destinationauthenticatedkey, pathLets the user pick a folder for a destination. Checks write permission, then saves the grant.
delegate.move_from_stagingauthenticateddelegation_id, job_id, staging, name, size, optional conflictMoves 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.getauthenticateddelegation_id, job_idState of a move.
delegate.operations.cancelauthenticateddelegation_id, job_idCancels a move.
delegate.operations.listauthenticatednone (send {})Lists moves.
delegate.example.exportauthenticatedkey, job_idExample export used by the save-later panel.
delegate.disable, delegate.enableadministratorsnoneTurn the integration off or on. Both end all grants.
delegate.auditauthenticatednoneEvents 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.

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.

OpDSM sessionParameters
delegate.auditRequired—
delegate.createRequiredjob_id?, ops, scope, path, ttl_seconds?, destination?, lifetime?
delegate.disableRequired—
delegate.enableRequired—
delegate.listRequired—
delegate.revokeOptionaldelegation_id, job_id?
files.cancelOperationOptionaldelegation_id, job_id
files.checkPermissionOptionaldelegation_id, job_id, path
files.copyOptionaldelegation_id, job_id, path, dest_dir, conflict?
files.createFolderOptionaldelegation_id, job_id, path, name, parents?
files.deleteOptionaldelegation_id, job_id, path, recursive?, permanent?
files.infoOptionaldelegation_id, job_id, path
files.listOptionaldelegation_id, job_id, path
files.moveOptionaldelegation_id, job_id, path, dest_dir, conflict?
files.moveFromStagingOptionaldelegation_id, job_id, staging, name, size, conflict?
files.operationStatusOptionaldelegation_id, job_id
files.readOptionaldelegation_id, job_id, path, max_bytes?
files.renameOptionaldelegation_id, job_id, path, name
files.taskCancelOptionaldelegation_id, job_id, task_id
files.taskStatusOptionaldelegation_id, job_id, task_id
files.writeOptionaldelegation_id, job_id, staging, dest_dir, name, conflict?
portal.registerRequired—
portal.unregisterRequired—
root.jobs.getRequiredjob_id
root.jobs.listRequired—
root.jobs.stopRequiredjob_id
root.runRequiredname, params?
root.startRequiredname, params?
statusOptional—

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 Routes
api.HandleRequest("reports.list", cfg.listReports),
reports.go
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" }
}
FunctionHandler signatureUse 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) errorUpload 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.

ItemUse
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 errorLogged on the NAS and returned to the UI as internal.

Derive owners from caller.User(). Never trust a user name sent in params.

You needUse
Files with the user’s permissionscfg.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 jobfilestation.RequestContext{Caller, Request, Service}.Files(), with List, Info, CreateFolder, CheckPermission and Write. Needs delegate.
File operations of a delegate jobdelegate.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 jobsCall 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 sessionDelegate. 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 operationThe 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.

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.

HelperWherePurpose
ctx.call(method, params)ctx (all UIs)Calls a backend method. Resolves with data, rejects with {code, message}.
ctx.localectxThe DSM language, such as en or zh-TW.
ctx.reopen(fn)ctxRuns fn when the window is shown again. Returns a function that removes it.
ctx.hostctxWindow and file manager actions: setTitle, openApp, openURL, openFileManager, revealFile, subscribe.
ctx.userctx{name, isAdmin} from DSM. Use it only to hide controls. The backend decides access.
ctx.filesctx{endpoint, token} for file transfers. Present when the app has files.
ctx.portalctxstatus(), apply(alias), disable(), openControlPanel(), mountPanel(el). Present when the app declares portal.
ctx.executorctxAdministrator 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/sdkFile provider for the picker: shares, list, info, createFolder, upload, download.
createSession(ctx)@synopackageland/sdk/vueLogin session state and calls: login, bootstrap, logout, call.
createBackgroundSaveFacade(ctx)@synopackageland/sdkWraps the delegate.* methods for the save-later panel.
FilesPage, FileBrowser, NasPicker@synopackageland/sdk/vueVue components for browsing and choosing files and folders.
LoginPage@synopackageland/sdk/vueVue sign-in form. It needs https:.
BackgroundPanel@synopackageland/sdk/vueVue panel for jobs, built on bg.browse.
ExecutorSettings@synopackageland/sdk/vueVue panel where an administrator approves background access.
ExecutorApproval@synopackageland/sdk/vueHolds 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/vuetrue 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/vueVue panel for the portal alias.
// A Vue component with ctx as a prop
const 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"
}
LimitValue
Request body1 MiB
Time for one request20 seconds
Methods in permissions.json1024, file at most 64 KiB
List page100 entries
Upload size16 GiB
App login session15 minutes at most
Sign-in attempts5 per minute per account, 20 per minute per address