Flask Blueprint and Route Registration#
All Flask blueprints in Dify's API layer are registered in a single place: ext_blueprints.init_app(). This function is called during app startup by initialize_extensions(), which runs the full extension chain in a fixed order — ext_blueprints runs after database, storage, and other infrastructure extensions are ready .
Registered Blueprints and URL Prefixes#
Eight blueprints are registered, each with a distinct URL prefix:
| Blueprint | URL Prefix | CORS |
|---|---|---|
service_api | /v1 | Yes (Bearer auth) |
openapi | /openapi/v1 | Yes (configurable origins, credentials) |
web | /api | Yes (split: embed vs. authenticated) |
console | /console/api | Yes (credentials) |
files | /files | Yes |
inner_api | /inner/api | No |
mcp | /mcp | No |
trigger | /triggers | Yes |
openapi_bp is only registered when dify_config.OPENAPI_ENABLED is set . inner_api_bp and mcp_bp are registered without CORS .
CORS is applied via the idempotent helper _apply_cors_once(), which guards against double-application across multiple app instances using a _dify_cors_applied flag on the blueprint object.
Module Import Timing#
Controller blueprint imports happen inside init_app(), not at module level . This late-binding pattern prevents circular imports: route modules depend on services and models that aren't fully initialized when ext_blueprints.py is first parsed.
ExternalApi and Namespace Pattern#
Most blueprints wrap their Flask Blueprint with ExternalApi — a custom flask_restx.Api subclass from libs/external_api.py. ExternalApi adds standardized error handling, Bearer token authorization defaults, and Swagger UI configuration. It also overrides private flask-restx methods to scope 404 errors to the blueprint's prefix, preventing route enumeration.
Route classes then use a flask_restx.Namespace for grouping — e.g., inner_api_ns = Namespace("inner_api", path="/") registered against the ExternalApi instance .
inner_api Blueprint#
The inner_api blueprint at /inner/api is the internal/enterprise control plane . It is intentionally excluded from CORS and license-gating , since blocking it would break workflow execution and license recovery. Sub-modules imported in controllers/inner_api/__init__.py cover:
- Plugin invocations (
plugin/plugin.py) — backwards-invocation of LLM, tools, apps, encryption, summary, file upload/download - Agent config (
plugin/agent_config.py) — Agent Soul config versioning endpoints - Knowledge retrieval (
knowledge/retrieval.py) - App DSL (
app/dsl.py) - Mail (
mail.py) - Runtime credentials (
runtime_credentials.py) - Workspace (
workspace/workspace.py)
Routes within inner_api are decorated with @plugin_inner_api_only for authentication . The __all__ export in __init__.py lists every sub-module to prevent linters from flagging the side-effect imports .
License Gating#
app_factory.py registers a before_request hook that checks the enterprise license for most surfaces. /inner/api, /files, and /health are explicitly excluded from license gating to avoid blocking workflow execution or license recovery .
Key Entry Points#
| File | Purpose |
|---|---|
api/extensions/ext_blueprints.py | Central registration, CORS config |
api/app_factory.py | Extension init order, license-gating hook |
api/controllers/inner_api/__init__.py | inner_api blueprint + sub-module imports |
api/libs/external_api.py | ExternalApi wrapper (error handling, Swagger) |