18 KiB
title
| title |
|---|
| apps (get / describe / run) |
apps
Implementation: see
cli/src/. Build & test: seecli/README.md.
App-resource commands. Split into two CLI surfaces matching server subject_type separation:
| Subject | CLI surface | Server surface |
|---|---|---|
dfoa_ (account) |
get apps, get app, run app |
/openapi/v1/apps* |
dfoe_ (external SSO, EE only) |
get permitted-external-apps, get permitted-external-app, run permitted-external-app |
/openapi/v1/permitted-external-apps* |
CLI dispatches client-side based on the cached subject_type from hosts.yml (written at auth login from GET /openapi/v1/account). Cross-surface invocation errors before any network call:
dfoa_token +get permitted-*→error: 'permitted-external-apps' commands are for external SSO sessions; use 'get apps' instead. Exit 2.dfoe_token +get app*/run app→error: 'apps' commands are for account sessions; use 'get permitted-external-apps' instead. Exit 2.
Companion: auth.md §HTTP contract, server/endpoints.md §OpenAPI — app (two surfaces, strict subject_type separation). Agent onboarding text (help account / help external) in guide.md.
Command surface
dfoa_ surface (account sessions; CE + EE)
| Command | Verb | Purpose |
|---|---|---|
difyctl get apps |
get | List apps in target workspace |
difyctl get app <id> |
get | Single-app metadata fetch (slim) |
difyctl describe app <id> |
describe | Rich detail: info + parameter schema + JSON Schema |
difyctl run app <id> [message] |
run | Invoke app; server dispatches by apps.mode |
dfoe_ surface (external SSO; EE only)
| Command | Verb | Purpose |
|---|---|---|
difyctl get permitted-external-apps |
get | List apps the SSO subject can access (no workspace) |
difyctl get permitted-external-app <id> |
get | Single permitted-external-app metadata fetch |
difyctl describe permitted-external-app <id> |
describe | Rich detail for a permitted app |
difyctl run permitted-external-app <id> [message] |
run | Invoke a permitted app |
get apps (dfoa_)
difyctl get apps [-w <ws>] [-A] [--mode <m>] [--name <s>] [--tag <t>...] [--limit N] [-o <fmt>]
Pagination is hidden by default — auto-paginates internally. Users specify --limit (total rows wanted); the client issues as many server pages as needed and stops at --limit or when has_more=false. No user-facing --page flag; see §Pagination below.
Flow
- Read active session from
hosts.yml(bearer + subject_type + current workspace). - Pre-flight:
subject_type == "account"? Else → cross-surface error (exit 2). - Resolve
workspace_id:-w/--workspaceflag →DIFY_WORKSPACE_IDenv →hosts.yml.workspace.id. Unresolved → exit 2workspace_required. GET /openapi/v1/apps?workspace_id=<ws>&page=N&limit=N&mode=...&name=...&tag=.... Bearer auth, scopeapps:read.- If
-A/--all-workspaces: fan out acrossavailable_workspaces, max 4 concurrent. Concatenate. - Render per
-o.
get app <id> (dfoa_, single)
difyctl get app <id> [-w <ws>] [-o <fmt>]
- Pre-flight:
subject_type == "account". Else cross-surface error. - Resolve
workspace_id(required by server on this surface). GET /openapi/v1/apps/<id>/describe?fields=info&workspace_id=<ws>. Bearer auth, scopeapps:read. Slim variant —parameters+input_schemaskipped server-side.- Render per
-o.
Flags
| Flag | Default | Notes |
|---|---|---|
-w, --workspace |
resolved per chain | Override current workspace. Required for get app <id> |
-A, --all-workspaces |
false | Client-side fan-out across all member workspaces. Visits each workspace with same auth, applies same scope + ACL filter |
--mode |
(all) | chat / completion / workflow / agent-chat / advanced-chat |
--name |
— | Substring match |
--tag (repeatable) |
— | Tag name (not UUID); server resolves within workspace |
--limit |
defaults.limit (50) |
Total rows across auto-paged calls. Max 10000 |
-o, --output |
defaults.format (table) |
table / json / yaml / wide / name |
get permitted-external-apps (dfoe_, EE only)
difyctl get permitted-external-apps [--mode <m>] [--name <s>] [--limit N] [-o <fmt>]
No workspace concept. No -w flag. No -A (single deployment-wide query). No --tag (tags are tenant-scoped; dfoe_ is cross-tenant).
Flow
- Pre-flight:
subject_type == "external_sso". Else cross-surface error. GET /openapi/v1/permitted-external-apps?page=N&limit=N&mode=...&name=.... Bearer auth, scopeapps:read:permitted-external. Strict server validator — extra params → 422.- Auto-paginate until
--limitorhas_more=false. - Render per
-o.
get permitted-external-app <id> (dfoe_, single)
difyctl get permitted-external-app <id> [-o <fmt>]
- Pre-flight:
subject_type == "external_sso". GET /openapi/v1/permitted-external-apps/<id>. Slim metadata. 404 if app not in permitted set.
Flags (dfoe_ list)
| Flag | Default | Notes |
|---|---|---|
--mode |
(all) | Same enum as dfoa_ surface |
--name |
— | Substring match |
--limit |
50 | Total rows. Max 10000 |
-o, --output |
table | table / json / yaml / name (no wide — no workspace columns to show) |
Pagination
Pagination is implementation detail, not user vocabulary. The flag is --limit N (total rows wanted). The client loops the server's {page, limit, total, has_more, data} envelope until either --limit rows have been collected or the server reports has_more=false.
Server-side: each /openapi/v1/apps call is capped at MAX_PAGE_LIMIT (200) rows per page. The CLI computes per-call limit as min(remaining, MAX_PAGE_LIMIT) and increments page until done.
Behavior summary:
--limit 10→ 1 server call,?page=1&limit=10. Returns ≤10 rows.--limit 50(default) → 1 server call,?page=1&limit=50. Returns ≤50 rows.--limit 200→ 1 server call,?page=1&limit=200.--limit 500→ up to 3 server calls (page=1&limit=200,page=2&limit=200,page=3&limit=100); short-circuits if any page returnshas_more=false.--limit 0→ reserved (rejected withconfig_invalid_value); use a positive integer or omit for the default.
Single-page raw fetch is not exposed. Agents that need page-by-page parsing should consume the JSON envelope from the server directly (-o json always returns the assembled data: [...] after auto-paging — no page/has_more keys leak into the CLI's stdout).
Output format implications:
table/wide/name: rows stream to stdout as each server page arrives, with one final summary line on stderr (fetched 312 of 312 rows) when the loop ends.json/yaml: assembled into a single payload at end-of-loop and emitted once. The wire-level{page, limit, total, has_more}keys do not appear in CLI output; only the mergeddata: [...]array.
Errors mid-loop:
- 429 from server: respect
Retry-Afterheader, sleep, retry up to 3 times. After exhaustion → exit 1 with the rows already collected printed (-o table/wide/name) or a partial-payload error envelope (-o json/yaml). - 5xx mid-loop: same treatment as 429 (exponential backoff, retry).
- 4xx mid-loop (other than 429): abort, surface error to stderr, exit per
auth.md §Error handling.
Output — list
Default text/table:
ID MODE NAME DESCRIPTION
app_a1b2 chat Customer FAQ Answers product FAQ from KB...
app_c3d4 workflow Daily Report Generates daily ops summary...
Description column truncates to fit terminal width (terminal width minus other column widths). Trailing ellipsis. Sort: updated_at DESC.
| Format | Columns / payload |
|---|---|
table (default) |
ID MODE NAME DESCRIPTION (description truncated) |
wide |
ID MODE NAME DESCRIPTION TAGS UPDATED AUTHOR (no truncation) |
name |
IDs only, one per line. Pipeable: difyctl get app -o name | xargs -I{} difyctl describe app {} |
json |
Full server payload, indented |
yaml |
Full server payload |
With -A: prepend WORKSPACE column to table/wide; JSON/YAML wrap each row with {workspace_id, ...row}.
Output — single
Same shape as list except data is a one-element array. name format emits the single ID.
Error handling
| Server code | CLI behavior |
|---|---|
403 wrong_surface |
error: this surface accepts only account sessions; you're signed in as <subject_type>. Exit 1. Should be caught client-side; surfacing it means subject_type cache is stale — CLI clears and prompts re-login. |
403 insufficient_scope |
error: token lacks scope for this operation (required=apps:read). Exit 1 |
402 license_required (dfoe_ surface only) |
error: this deployment's enterprise license does not cover external SSO apps surface. Exit 1 |
| 401 | Per auth.md §No token refresh — clear creds, exit 4 |
| 404 (single fetch) | error: app not found (app_id=<id>). Exit 1 |
describe app (dfoa_) / describe permitted-external-app (dfoe_)
difyctl describe app <id> [-w <ws>] [-o <fmt>] # dfoa_
difyctl describe permitted-external-app <id> [-o <fmt>] # dfoe_
Surface-specific. Cross-surface invocation errors client-side (exit 2).
Flow — describe app (dfoa_)
- Pre-flight:
subject_type == "account". Else cross-surface error. - Resolve
workspace_id(required by server on this surface). GET /openapi/v1/apps/<id>/describe?workspace_id=<ws>. Bearer auth, scopeapps:read.- Render per
-o.
Flow — describe permitted-external-app (dfoe_, EE only)
- Pre-flight:
subject_type == "external_sso". Else cross-surface error. GET /openapi/v1/permitted-external-apps/<id>. Bearer auth, scopeapps:read:permitted-external.- Render per
-o. EE returns the same{info, parameters, input_schema}shape; tenant resolved from app row.
Default text output (kubectl-describe style)
ID: app_a1b2c3
Mode: chat
Name: Customer FAQ
Description: Answers product FAQ from knowledge base.
Author: alice@example.com
Tags: [prod, customer-facing]
Inputs:
industry (string, required)
Options: retail | finance | health
context (paragraph, optional)
Max length: 4000
File upload:
Image: enabled (max 5, ≤10MB)
Document: disabled
Conversation features:
Opening statement: "Hi! I can help you with product questions."
Suggested questions: 3 items
Output formats
| Format | Payload |
|---|---|
table (default) |
Sectioned indented label:value text |
json |
Server /describe response, indented |
yaml |
Same, YAML |
wide and name not applicable to describe (single resource); return NoCompatiblePrinterError.
Server endpoint: GET /openapi/v1/apps/<id>/describe — canonical "what is this app" surface. Returns {info, parameters, input_schema}; the openapi-namespace has no /info or /parameters routes (callers requiring slim subsets use ?fields=...). input_schema is JSON Schema (Draft 2020-12) derived server-side from user_input_form, intended for agent tool-call payload generation. Single-request failure mode: no partial-state handling needed.
run app (dfoa_) / run permitted-external-app (dfoe_)
difyctl run app <app-id> [message] [-w <ws>] [--input <k=v>]... [--conversation <id>] [--stream] [-o text|json|yaml]
difyctl run permitted-external-app <app-id> [message] [--input <k=v>]... [--conversation <id>] [--stream] [-o text|json|yaml]
Two commands, one runner under the hood. Surface routing identical to read paths — run app hits /apps/<id>/run, run permitted-external-app hits /permitted-external-apps/<id>/run. Cross-surface invocation errors client-side (exit 2).
Flow — run app (dfoa_)
- Pre-flight:
subject_type == "account". Else cross-surface error. - Resolve bearer + metadata from
hosts.yml. Bearer absent →not_logged_in, exit 4. - Resolve
workspace_id(-w→ env → ctx). Sent inAppRunRequestbody (informational). POST /openapi/v1/apps/<id>/run. Bearer auth, scopeapps:run.- Render response.
Flow — run permitted-external-app (dfoe_, EE only)
- Pre-flight:
subject_type == "external_sso". - Resolve bearer. Bearer absent → exit 4.
POST /openapi/v1/permitted-external-apps/<id>/run. Bearer auth, scopeapps:run. Noworkspace_idin body — server resolves tenant from app row.- Render response.
CLI does NOT pre-fetch to dispatch by mode — server owns mode dispatch. CLI's job: build AppRunRequest from <message> + --input + --conversation + --stream; render AppRunResponse per mode shape returned. The app_meta cache (full describe blob) is consulted opportunistically for client-side input validation; cache miss non-fatal — server validates authoritatively and returns 422 with required-input names if anything is wrong.
Universal enable_api gate
Both surfaces filter through _apply_openapi_gate (api/services/openapi/visibility.py). App where enable_api = false → 404 on both surfaces (no existence leak). See server/middleware.md §Universal openapi gate. No console escape hatch.
Headers
| Header | Value |
|---|---|
Authorization |
Bearer <token> |
Server resolves tenant from apps.tenant_id. App id is in URL path (no X-Dify-App-Id header here).
Input
- Positional
<message>— for chat / agent-chat / advanced-chat modes, the user message. Workflow mode rejects positional message; use--input. --input <key>=<value>(repeatable) — app inputs (variables configured in the app). For workflow runs, these are the workflow inputs.--conversation <id>— chat-mode only; resume an existing conversation. Stderr-printed conversation hint after first chat invocation:--conversation <new-id>.--stream— request SSE streaming.agent-chatalways streams (the upstream agent step requires it); for other modes, blocking when omitted. Text output streams deltas as they arrive;-o json/-o yamlaggregate into the blocking-shape envelope at end-of-stream.
Output
Format selected by -o|--output. Default text (human-readable).
| Format | Behavior |
|---|---|
text (default) |
Per-mode rendering — chat / agent-chat / advanced-chat: assistant reply to stdout, conversation hint to stderr; completion: assistant reply to stdout; workflow: data.outputs JSON to stdout |
json |
Raw server response to stdout, indented JSON |
yaml |
Raw server response to stdout, YAML |
wide and name reserved for collection commands; return NoCompatiblePrinterError on run app.
Not supported: --jq, mermaid output, table output for run results, live progress display, usage metrics breakdown.
Error handling
| Server code | CLI behavior |
|---|---|
403 wrong_surface |
Subject_type mismatch (should be caught client-side). Clear subject_type cache, prompt re-login. Exit 1 |
| 403 app ACL deny | error: you do not have access to this app (app_id=<id>). Exit 1 |
403 insufficient_scope |
error: token lacks scope for this operation (required=apps:run). Exit 1 |
402 license_required (dfoe_ surface only) |
error: this deployment's enterprise license does not cover external SSO apps surface. Exit 1 |
| 404 app not found | error: app not found (app_id=<id>). Exit 1 |
| 422 validation | Print server's validation message; surface required-input names as hints. Exit 1 |
| 401 | Per auth.md §No token refresh — clear creds, exit 4 |
App-info cache
Single 1h-TTL store holds the full {info, parameters, input_schema} blob keyed by (host, app_id). Slim ?fields=info calls populate / read the same key — partial blob is upgraded on the next full fetch. Shared by get app <id>, describe app, and run app (no per-command duplicate caches).
Default sort: updated_at DESC. Tag-name match (--tag prod) hits every tag with that name; -o json disambiguates via tag IDs. get apps -A caps parallel workspace fan-out at 4 concurrent calls to stay under per-token rate limits. CLI renderer keys on parameters.user_input_form (not input_schema).