Dify OpenAPI (/openapi/v1)#
/openapi/v1 is Dify's user-scoped, bearer-authenticated programmatic API — the primary interface for the difyctl CLI and external integrations. It is described internally as "User-scoped programmatic API (bearer auth)" and is distinct from the cookie-authenticated /console/api surface used by the web UI .
The blueprint is registered at api/controllers/openapi/__init__.py with two pieces of middleware attached at startup:
- Anti-framing security (via
attach_anti_framing) - Version gate (via
attach_version_gate) — rejects outdateddifyctlclients before they reach any endpoint
API Design: Resource-Oriented Paths and AIP-136#
As of Dify 1.16.0 (PR #38367), all /openapi/v1 paths follow REST resource-oriented conventions aligned with AIP-136 . Verb-suffix paths were replaced with standard GET resource reads and AIP-136 :action custom methods:
| Old (verb-suffix) | Current (resource-oriented) |
|---|---|
GET /apps/{id}/describe | GET /apps/{id} |
POST /apps/{id}/run | POST /apps/{id}:run |
POST /apps/{id}/tasks/{tid}/stop | POST /apps/{id}/tasks/{tid}:stop |
GET /apps/{id}/export | GET /apps/{id}/dsl |
GET /apps/{id}/check-dependencies | GET /apps/{id}/dependencies:check |
POST /workspaces/{ws}/switch | POST /workspaces/{ws}:switch |
PUT /workspaces/{ws}/members/{mid}/role | PATCH /workspaces/{ws}/members/{mid} |
POST /apps/{id}/files/upload | POST /apps/{id}/files |
This was a hard cutover — no dual-mount or /v2 strategy — because difyctl and the server ship in lockstep .
Endpoint Surface#
Each domain is implemented in its own module under api/controllers/openapi/ :
| Module | Paths | Notes |
|---|---|---|
apps.py | GET /apps, GET /apps/{id} | List and describe apps; supports UUID or name-based lookup |
app_run.py | POST /apps/{id}:run, POST /apps/{id}/tasks/{tid}:stop | Mode-agnostic app execution |
app_dsl.py | GET /apps/{id}/dsl, GET /apps/{id}/dependencies:check, POST /workspaces/{ws}/apps/imports, POST /workspaces/{ws}/apps/imports/{iid}:confirm | DSL export/import |
workspaces.py | GET /workspaces, GET /workspaces/{id}, POST /workspaces/{id}:switch, GET /workspaces/{id}/members, POST /workspaces/{id}/members, DELETE /workspaces/{id}/members/{mid}, PATCH /workspaces/{id}/members/{mid} | Workspace discovery + team member management |
account.py | GET /account, session list, token revoke | Account info |
files.py | POST /apps/{id}/files | File upload for app inputs; returns 400 for no file/multiple files/invalid filename/blocked extension, 413 for oversized files, 415 for unsupported file types |
human_input_form.py | GET /apps/{id}/human-input-forms/{tok}, POST …/{tok}:submit | Paused workflow human-input forms |
workflow_events.py | GET /apps/{id}/tasks/{tid}/events | SSE stream reconnect |
apps_permitted_external.py | GET /permitted-external-apps, GET /permitted-external-apps/{id} | External-SSO app discovery — EE only |
oauth_device.py | /oauth/device/* | RFC 8628 device-flow OAuth |
oauth_device_sso.py | /oauth/device/sso/* | SSO device-flow — EE only |
_meta.py | GET /_version | Unauthenticated version/edition probe |
index.py | GET /_health | Health check |
Workspace Collaboration and Team Member Management#
The workspace endpoints in workspaces.py are the bearer-authenticated counterpart to the console's cookie-auth workspace APIs. Key behavior:
GET /workspaces— lists workspaces for the authenticated account; external SSO tokens (noaccount_id) return an empty list .POST /workspaces/{id}:switch— server-side workspace switch used bydifyctl use workspace <id>. The CLI must abort on failure to preventhosts.ymldiverging from server state .GET /workspaces/{id}/members— paginated member list; accessible to any member .POST /workspaces/{id}/members— invite by email; requiresowneroradminrole, enforces billing/license limits, returns an activation URL .DELETE /workspaces/{id}/members/{mid}— remove a member; self-removal and owner-removal rejected as400.PATCH /workspaces/{id}/members/{mid}— update role; owner role unassignable here (ownership transfer is console-only) .
All write endpoints require WORKSPACE_WRITE scope and TokenType.OAUTH_ACCOUNT — external SSO subjects have no write access .
Authentication and Subject Model#
All endpoints except /_health and /_version use bearer token authentication via the auth pipeline in api/controllers/openapi/auth/ . Two token types exist:
dfoa_— Account tokens (TokenType.OAUTH_ACCOUNT): Full access including workspace write and app build/management operations.dfoe_— External SSO tokens (TokenType.EXTERNAL): Read-only access to permitted apps via/permitted-external-apps; no workspace membership and no write access .
PR #37641 formalized this split into two CLI "faces" :
appface (usage):describe,run,get,resume— dual-subject capable via anAppReaderabstraction that dispatches toAppsClient(account) orPermittedExternalAppsClient(external SSO) depending on the token.studio-appface (build):export,import— account-only.
The AppReader dispatch is implemented in cli/src/api/app-reader.ts .
Version Gating (difyctl ↔ Server Compatibility)#
Because the API surface undergoes breaking changes on hard cutovers, a symmetric version gate is enforced on both sides :
Server-side (api/controllers/openapi/_version_gate.py):
- Attached as a
before_app_requesthook on the/openapi/v1blueprint. - Rejects
difyctlclients older than0.2.0-alpha(paired with Dify1.16.0) with HTTP 426 Upgrade Required. - Fails open for non-
difyctlor unparseable User-Agent headers — never blocks non-CLI traffic. - Allowlists
/_versionand/_healthso outdated clients can discover the required upgrade.
Client-side (cli/src/version/enforce.ts):
- Probes
GET /_version(unauthenticated, 2-second timeout) before executing any command. - Rejects servers older than
1.16.0with exit code 6 (ErrorCode.VersionSkew); issues a soft nudge for too-new servers. - Caches the compatibility verdict per-host for 1 hour (positive-only cache; fresh servers clear it immediately).
- Fails open on probe timeout or network errors.
Together these gates convert confusing 404 errors (calling a removed path) into explicit upgrade messages .
Generated Contracts and Type Safety#
The CLI uses generated oRPC/Zod contracts that are committed to the repository under packages/contracts/generated/api/openapi/ . The codegen pipeline produces:
orpc.gen.ts— typed oRPC client with nested method paths (e.g.,apps.byAppId.runrather thanapps.appIdRun)types.gen.ts— TypeScript types derived from the OpenAPI schemazod.gen.ts— Zod validators for runtime request/response validation
The backend contract is defined via _contract.py decorators (@accepts, @returns) on each endpoint and backed by response/query models in _models.py . Generated Markdown documentation lives at api/openapi/markdown/openapi-openapi.md . Any route change requires regenerating these contracts.