Skip to content

API reference

Every entry, its arguments and results.

Use client.cap.call for capability reads/mutations and client.cap.watch/live for capability subscriptions. A script receives cap only. These are typed entry names, not Electron channel names.

Arguments and results are JSON objects. Optional fields must be omitted when unused, not set to undefined or an unsupported null. Closed schemas reject extra fields. Page limits are 1–100 unless stated otherwise. Opaque cursors come from the previous response and must not be constructed by the app.

For desktop capability calls, a revoked resource grant or expired caller stops waiting and prevents result delivery. Changing an Allow grant to Ask also ends that call. A canceled write can already have reached its target; read current state before an explicit retry. Apps cannot supply the host's invocation context or provider credentials in arguments.

Resource selection

Call resources.list({action, limit?}) to list resource IDs and names on which the bound app already has Allow. Ask and Off resources are excluded. The result has resources and complete; an empty list does not prove that no accounts are connected. There is no separate discovery grant. Use selection rules and limits before building an account or collection picker.

Website data

Use Apple Calendar reads for calendar.listCalendars and calendar.listEvents. Declare apple-calendar.read. Select observed calendar IDs and an explicit time range of at most 31 days. These reads use this desktop's managed native resource and require system permission as well as app Access.

Use Google Connections and the complete Google API catalog for the 144 retained native Gmail v1, Drive v3 and Docs v1 methods, with separate Ask Permissions for reading, editing, deletion, sharing, settings and subscriptions. The raw sends gmail.users.messages.send and gmail.users.drafts.send are retired. Use gmail.sendMessage to send new email with the Send email Permission. Native method calls use named parameters plus body/media and return { data, media }. The existing bounded helpers are gmail.listMessages, gmail.getMessage, drive.listFiles and docs.getDocument. These use separate Google account resources and Ask read actions. Gmail returns metadata only; Drive returns file metadata; Docs returns bounded plain tab body text.

Use notifications.show({title, body, silent?}) to submit an app-identified desktop alert. Declare notifications.show; Desktop notifications defaults to Ask. See notification delivery and limits.

Use Desktop clipboard for explicit plain-text copy/paste actions with clipboard.readText and clipboard.writeText.

For public API and feed data without browser sign-in, use web.fetch and web.feed. Use clock.now for the current desktop time and IANA time zones. See Clock and public data for permissions, examples and limits.

Both views and scripts can call browser.inspect({url, scope?, limit?}) and browser.extract({recipe, inputs}). Declare browser.read. To click an observed button and wait for a modal before extraction, call browser.workflow({recipe, inputs, steps}) and declare browser.interact. The existing Workspace browser resource controls these operations. See website commands for the closed recipe schema, bounded results, origin checks, and the script/skill/schedule workflow. A reusable website command is an app script, not a dynamically registered capability.

For full browser automation and diagnostics, use browser.openSession, browser.cdp, browser.events, and browser.closeSession. Declare browser.debug for page code and network/debugging access. See the raw Browser tools guide for session ownership, CDP calls, capture, limits, and a script example.

An MCP server that the owner adds in Connections gives one function for each approved tool: entry mcp.<slug>.<toolId> with permission mcp-<slug>.<toolId>. Call it with { connection?, arguments }. See MCP server functions for names, results, errors and limits.

Collection records

Both views and scripts can call these entries. Use database:<app-id>:<collection-name>, for example database:task-counter:tasks. A short name such as tasks is not the collection connection ID. The app needs the listed action and resource visibility. Another app's connection cannot be obtained by changing the ID string.

EntryKindArgumentsResultAction
database.getreadconnection, id{ record }, nullabledatabase.read
database.listreadconnection, optional cursor, limit{ records, nextCursor }database.read
database.putmutateconnection, id, body, expectedRevision, UUID requestKeyMutation result belowdatabase.write
database.deletemutateconnection, id, expectedRevision, UUID requestKeyMutation result belowdatabase.delete

A record contains id, revision, body, createdAt, and updatedAt. The body is a scalar-valued object that must match the collection's registered schema. A put replaces the record body; it is not a partial merge. New records use expected revision 0; edits use the revision read from the host. A deleted ID can retain revision metadata, so 0 is not a way to recreate any arbitrary old ID.

Mutation results are { state: 'written', record }, or { state: 'deleted' | 'absent' | 'conflict', revision }. A record conflict is a returned state, not a thrown SDK error. Reload the current data and obtain a new intent before saving a changed payload. Exact retries keep the full original payload, revision, ID and request key.

There is no collection record subscription or general SQL/query API. Use paged list/get reads and refresh the view after a write.

Virtual files

Each VFS mount is a separate Access connection: fs:mount:apps, fs:mount:skills, fs:mount:references, or fs:mount:<custom-name>. Views and scripts use client.cap.call / cap.call. Declare fs.read and/or fs.write; both default to Ask. The host derives the connection from the path; a grant covers that mount. There is no per-folder ACL.

EntryKindArgumentsAction
fs.statreadpathfs.read
fs.searchreadpath, literal query, optional limit, caseSensitivefs.read
fs.listreadpath, optional cursor, limitfs.read
fs.readreadpath, optional encoding, offset, length, expectedRevisionfs.read
fs.writemutatepath, content, expectedRevision, requestKey; optional encoding, offset, truncatefs.write
fs.mkdirmutatepath, requestKey; optional parentsfs.write
fs.renamemutatepath, to, expectedRevision, requestKeyfs.write
fs.removemutatepath, expectedRevision, requestKey; optional recursivefs.write
apps.describeread{}No mount action
apps.previewmutateSource directory path, expectedRevisionfs.read
apps.loadmutateSource directory path, expectedRevisionfs.loadApp
apps.updatemutateappId, source path, expectedRevision, expectedInstalledRevisionfs.loadApp

apps.describe returns the static manifest schema, an index of the SDK contracts (the full schemas are in /references/sdk/ files), instructions, and a runnable starter. apps.preview creates a fixed source review in Advanced › Developer › Apps from virtual files. It runs no code. The owner can inspect the source and select Load app or Update app from that review. Chat cannot open app pages before install. apps.load reads and installs the specified source revision through AppCatalog; fs.loadApp defaults to Ask on each mount and covers reading and loading the package. The loaded app gets its own grants and collections, and schedules start paused. See VFS app loading.

Mutations return { state: 'ok' | 'conflict', entry }. Revisions are opaque strings; create-only writes use null. Keep the exact payload and key for a retry. Reads return { entry, encoding, content, nextOffset }; listings return { directory, entries, nextCursor }. No subscription is provided. Examples and limits, contract schemas.

Owner vault

Both views and scripts can call the vault entries when their app declares the action and Access permits it. The Access connection is vault:<vault-id>. Omit vault to use the default vault.

EntryKindArgumentsResultAction
vault.listreadOptional vault{ vault, items }vault.see
vault.savemutateOptional vault, item, UUID requestKey{ itemId }vault.edit

vault.list returns the id, kind, title and fields of each vault item. A field has its label, secret, and a reference (vault:<vault>/<item>/<field>); the list does not return values. vault.save takes an item with kind, title, optional websites and fields. An app or agent cannot send a secret value (secretValue): the owner sets passwords and keys in Vault. An app or agent also cannot change the websites of a sign-in that exists ("Change the websites of a sign-in in Vault."). Keep the exact payload and key for a retry.

Sign in to a website

A sign-in is a turn of a conversation: there is no sign-in entry. An app that declares ai.use and vault.signIn gets the built-in profile sign-in. Send it the page URL with agent.send({ profile: 'sign-in', model, message: 'Sign in to https://www.costco.com/LogonForm', requestKey }), then read the turn with agent.turn or agent.watch. The turn runs with your app's Access and models. Redac finds the saved sign-in of the website in the vault that Access allows; when it must, it asks the owner to add one or to choose one. Cards for a code, a new password or another value show in the app chat, in Alerts and on the phone (agent.interactions). The tabs share their cookies, so your later browser calls are signed in. The answer of the turn says whether the sign-in worked; it has no value and no reference.

Your own profile can sign in too: list sign_in_start and the session tools (see the AI tool catalog) and declare vault.signIn. sign_in_start opens a session bound to one sign-in in its own tab. While the session is open, the turn's other tools are refused; sign_in_done ends it. A session has at most 30 tool calls and 5 minutes, and a turn opens at most 3 sessions.

Agent and AI

Both pages and scripts can use these APIs for their own app. They talk to the app's agent in app chats: chats with one of the app's profiles, shown in the owner's Chat list in one group for the app. They are internal app APIs with handler-enforced ownership, not an agent connector. AI use still requires ai.use on the selected AI connection. Tools require their declared actions. An app chat uses the app's Access, never Chat's.

EntryKindArgumentsResult
agent.modelsread{}{ connections: [{ connection, label, models: [{ ref, id, name }] }] }
agent.sendmutateManifest profile name, model ref, message, UUID requestKey, optional chatId{ chatId, turnId }
agent.turnreadturnIdTurn
agent.watchsubscribeturnIdTurn
agent.stopmutateturnId{ state: 'cancel_requested' | 'already_terminal' }
agent.interactionssubscribeturnId{ interactions }
agent.answermutateinteractionId, nonempty answer, UUID requestKey{ state: 'answered' }
agent.chatsreadOptional cursor, limit (at most 100, default 20){ chats: [{ chatId, title, updatedAt, latestTurn }], nextCursor }

A turn is one message to the agent and the work until its answer: { chatId, turnId, state, text, error, pendingInteractions }. text is the answer text so far. States are queued, running, completed, failed, cancelled, or interrupted. A send receipt means acceptance, not completion. Watch or read the turn to get the answer. A message is at most 256 KiB. A repeated request key gives the first receipt.

agent.send with no chatId makes a new app chat. With chatId it continues that chat with the same profile; the agent of a chat cannot change. A page can continue the chats that pages made and the chats that the owner wrote in. A script can continue only the chats that the same script run made. One chat has one live turn at a time; a send into a busy chat is refused with unavailable. An app has at most 4 live turns, and all apps together at most 32 (rate_limited). When the app is turned off, its chats refuse new messages ("This app is turned off.").

A script's send is a child of the script run. The run waits for its children before it ends, and a child that does not complete fails the run with child_failed. Stopping the run stops its children, and a child's approvals end at the run's deadline. A child's questions show in the run's pendingInteractions. A run has at most 32 children.

agent.answer answers a question of a turn of this app; a script answers and stops only its own children. It cannot approve an Access request. The owner answers approval cards. agent.turn of a removed chat gives not_found.

The model ref is <connection-id>/<model-id>. Let the user select from agent.models; do not hardcode current model names or provider credentials. The catalog is bundled with Pi, not a live provider list. Public execution requires piiPolicy: 'redact'; known-value redaction does not guarantee removal of unknown sensitive text.

AI tool catalog

A profile's tools list selects host tools. An SDK call such as database.list and an AI tool such as database_list are different interfaces.

General app toolEntry or serviceRequired manifest action
vault_listvault.listvault.see
vault_savevault.savevault.edit
sign_in_startOpens a sign-in session in its own tabvault.signIn
sign_in_read, sign_in_click, sign_in_key, sign_in_fill, sign_in_fill_code, sign_in_ask, sign_in_image, sign_in_click_at, sign_in_type, sign_in_doneThe tools of the open sessionNone (the session checked Access)
database_get, database_listRecord readsdatabase.read
database_putRecord replacementdatabase.write
database_deleteRecord deletiondatabase.delete
fs_stat, fs_list, fs_readVirtual metadata, directory and byte readsfs.read
fs_write, fs_mkdir, fs_rename, fs_removeVirtual disk changesfs.write
apps_describeStatic SDK guide, contract index and starterNone
apps_previewFixed source review for install or updatefs.read
apps_updateReplace the installed app version, including older codefs.loadApp
apps_loadLoad source as an installed appfs.loadApp
ask_userHost question interactionNo resource action for the question itself

Host validation rejects unknown tool names, a tool reserved for another app, or a tool whose action is absent from the manifest. An app profile can use the raw browser tools browser_openSession, browser_cdp, browser_events and browser_closeSession when the manifest declares browser.debug (browser_inspect and browser_extract need browser.read; browser_workflow needs browser.interact). An app profile can also use the scripts_* tools (they need scripts.run) and the schedules_* tools (they need schedules.manage). Only Chat can use update_plan, cite_sources, send_file_to_phone and activity_recent. Add host support first if an imported app needs a new effect.

Script and schedule capabilities

Chat, app views, and app agent profiles use the same capability dispatcher. Declare scripts.run to run a script, read its results, or cancel it. The resource is scripts:<target-app-id>. Declare schedules.manage to manage script schedules on schedules:<target-app-id>. The owner app has Allow for actions it declares. An app with a manifest reaches another app's data only through its manifest uses field (see Use another app's data). Access lists only declared data of another app. Declared database data starts at Ask. Scripts and schedules of another app start Off in Access. The install review sets Ask on them when the user keeps the Scripts or Schedules switch on. If the owner app installs after the declaring app, the declared rows start at their defaults. Chat keeps its script and schedule rows for other apps; they start Off. Ask requests approval for each call. These grants never give the target app more effect permissions.

EntryKindArguments
scripts.catalogreadOptional target appId, cursor, limit (up to 20)
scripts.runmutateappId, definitionRevision, script, input, UUID requestKey
scripts.getreadappId, runId
scripts.listreadappId, optional cursor, limit
scripts.watchsubscribeappId, runId
scripts.cancelmutateappId, runId
schedules.createmutateTarget appId, revision, name, cron, zone, script target, UUID key
schedules.listreadappId, optional paging and includeRemoved
schedules.getreadappId, schedule id, optional occurrence paging
schedules.updatemutateappId, id, expectedVersion, definitionRevision, changes
schedules.removemutateappId, id, expectedVersion
schedules.setEnabledmutateappId, id, expectedVersion, enabled

Catalog returns public installed definitions, input schemas, and revisions; it does not grant execution or expose run data. Use client.cap.call for reads and writes, and client.cap.watch/live for the run subscription. Script snapshots include initiatorAppId. The target app can read all its runs. Another app can read or cancel only runs that it started. It can manage only schedules that it created. Retry keys are scoped to caller and target. New schedules are paused. Automatic delegated runs need the creator's Run scripts and Manage schedules grants set to Allow at each admission, and a live creator app. Script effects use the installed target app's own grants. Actual scripts cannot start other scripts or manage schedules.

The owner can use the Functions tab of app details for run inputs, results, history, and paused schedule creation. App Access links to the same grant store.

This is contract version 3. The old script platform API and Chat-only tool names are removed. There are no aliases. Update app packages and saved calling skills that use the old API. Existing run history and schedules remain.

Privileged APIs and unavailable surfaces

SurfaceWho may call it
vfs.mounts, vfs.mountOwner shell only; apps cannot register mounts
app.source.*Owner shell only: review, watch, read, open, layout, stop, install, discard
scriptRuns.*Owner shell, not ordinary app views
scheduleAdmin.*, scheduleOwner.*Owner shell and explicitly scoped bundled Schedules view; see scheduling
interactions.pending/resolveOwner interface for questions and approvals, not an ordinary app client
Desktop chat.* / conversations.*Shell Chat interface, not app conversation storage
Browser, workspace, settings, update and package administrationHost/shell interfaces, not automatically exposed by default AppClient

Use client.availability.supports(bridge, name) to detect a served entry, then handle refusal. This is not a permission grant. Type exports or names in the generated API list do not remove runtime caller checks. See PII protection before handling owner data.

Synced from the Redac Desktop developer guide on 2026-10-30 · SDK contract version 23