Skip to main content
Version: 1.13.x

Catalog and model policy

On a shared Langflow server, anyone who can build a flow can use every component and model.

Superusers can create catalog and model policy to hide components, templates, model providers, and models from the Langflow visual builder.

OSS Langflow has no visual editor for policy management. Instead, use the APIs on this page with a superuser API key or session. This is the same credential the Users API requires.

An empty policy is the default, and has no restrictions. Nothing is restricted until a superuser writes a policy.

The six lists of restrictions are saved in one policy bundle. Replace the policy bundle with a PUT request. Send every list you want in effect with the PUT request. Send [] for a list you want unrestricted.

You want to…FieldExample
Hide a component and refuse flows that use itblocked_​component_​keysPythonREPLComponent (Python Interpreter)
Hide a starter templateblocked_​template_​keysbasic_​prompting
Allow only some providersapproved_​provider_​ids["openai", "anthropic"]. [] means every provider.
Block a modelblocked_​model_​keysgpt-4o, openai::gpt-4o, or openai::llm::gpt-4o
Allow only some integrationsapproved_​integration_​provider_​ids["google", "slack"]. [] means every integration.
Block one integration actionblocked_​integration_​action_​keysintegrations.google.drive.delete

A blocked component disappears from the palette. Saving or running a flow that still contains the component fails.

A blocked template is omitted from the starter list.

An integration outside approved_integration_provider_ids, or whose every action is in blocked_integration_action_keys, disappears from the palette and from GET /api/v1/integrations. Running a saved flow that still uses it fails before the provider is contacted, and creating or authorizing a connection for it returns 403.

tip

Catalog policy hides or refuses named components, including built-ins.

To block creating and editing custom code, set LANGFLOW_ALLOW_CUSTOM_COMPONENTS. To block built-in code-execution components at runtime without a policy bundle, set LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS.

Apply a policy​

tip

Apply any database migrations before sending requests to these endpoints. An uninitialized bundle returns 503.

  1. Create a superuser API key.

  2. Query how many flows a component would affect before you block it.

    GET /api/v1/catalog-policy/usage returns the number of affected flows. GET /api/v1/catalog-policy/usage/flows?component=PythonREPLComponent lists the affected flows.

    For field names and key formats, see How to name what you block.

  3. Read the current policy bundle, and copy revision.

    import os

    import requests

    url = f"{os.environ['LANGFLOW_URL']}/api/v1/policy-bundle"
    headers = {
    "accept": "application/json",
    "x-api-key": os.environ["LANGFLOW_API_KEY"],
    }

    response = requests.get(url, headers=headers)
    response.raise_for_status()
    print(response.json())
    const url = `${process.env.LANGFLOW_URL}/api/v1/policy-bundle`;

    const response = await fetch(url, {
    method: "GET",
    headers: {
    accept: "application/json",
    "x-api-key": process.env.LANGFLOW_API_KEY,
    },
    });

    if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
    }
    console.log(await response.json());
    curl -X GET \
    "$LANGFLOW_URL/api/v1/policy-bundle" \
    -H "accept: application/json" \
    -H "x-api-key: $LANGFLOW_API_KEY"
    Result
    {
    "revision": 1,
    "initialized": true,
    "source": "default",
    "approved_provider_ids": [],
    "blocked_component_keys": [],
    "blocked_template_keys": [],
    "blocked_model_keys": [],
    "content_hash": "ef74cccb21593ac02fc514e669c911768e740b1135d7a91d3fb15f72837ab762",
    "created_at": null,
    "created_by": null,
    "reason": null,
    "rollback_of_revision": null,
    "managed_externally": false
    }
    tip

    If managed_externally is true, an external controller owns policy. Reads still work. Writes return 409.

  4. Replace the bundle with a PUT request that includes every list you want in effect. Send the revision number copied from step 3 as expected_revision. Send [] for a list you want unrestricted.

    import os

    import requests

    base = os.environ["LANGFLOW_URL"]
    headers = {
    "accept": "application/json",
    "Content-Type": "application/json",
    "x-api-key": os.environ["LANGFLOW_API_KEY"],
    }

    current = requests.get(f"{base}/api/v1/policy-bundle", headers=headers)
    current.raise_for_status()
    revision = current.json()["revision"]

    payload = {
    "expected_revision": revision,
    "approved_provider_ids": ["openai", "anthropic"],
    "blocked_component_keys": ["PythonREPLComponent"],
    "blocked_template_keys": [],
    "blocked_model_keys": ["openai::llm::gpt-4o"],
    "reason": "Limit providers and block code execution",
    }

    response = requests.put(f"{base}/api/v1/policy-bundle", headers=headers, json=payload)
    response.raise_for_status()
    print(response.json())
    const base = process.env.LANGFLOW_URL;
    const headers = {
    accept: "application/json",
    "Content-Type": "application/json",
    "x-api-key": process.env.LANGFLOW_API_KEY,
    };

    const current = await fetch(`${base}/api/v1/policy-bundle`, { headers });
    if (!current.ok) {
    throw new Error(`HTTP ${current.status}`);
    }
    const revision = (await current.json()).revision;

    const response = await fetch(`${base}/api/v1/policy-bundle`, {
    method: "PUT",
    headers,
    body: JSON.stringify({
    expected_revision: revision,
    approved_provider_ids: ["openai", "anthropic"],
    blocked_component_keys: ["PythonREPLComponent"],
    blocked_template_keys: [],
    blocked_model_keys: ["openai::llm::gpt-4o"],
    reason: "Limit providers and block code execution",
    }),
    });

    if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
    }
    console.log(await response.json());
    curl -X PUT \
    "$LANGFLOW_URL/api/v1/policy-bundle" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $LANGFLOW_API_KEY" \
    -d '{
    "expected_revision": 1,
    "approved_provider_ids": ["openai", "anthropic"],
    "blocked_component_keys": ["PythonREPLComponent"],
    "blocked_template_keys": [],
    "blocked_model_keys": ["openai::llm::gpt-4o"],
    "reason": "Limit providers and block code execution"
    }'

    If another user updated the policy first, the PUT returns 409. Repeat from step 3 with an updated revision number.

    {
    "detail": {
    "message": "Policy bundle revision conflict",
    "expected_revision": 1,
    "active_revision": 2
    }
    }
  5. Optional: Undo a prior revision.

    GET /api/v1/policy-bundle/history lists prior bundle policy revisions.

    To restore a revision, POST to /api/v1/policy-bundle/rollback/{revision} with the current expected_revision:

    curl -X POST \
    "$LANGFLOW_URL/api/v1/policy-bundle/rollback/1" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $LANGFLOW_API_KEY" \
    -d '{"expected_revision": 2, "reason": "Revert last change"}'

How to name blocked items​

Components use the type stored in the flow JSON, such as PythonREPLComponent or ChatInput. That is the component's class identity, not the label in the visual builder. Keys are case-sensitive. Older aliases still match, so blocking PythonREPLComponent also catches nodes saved under a previous type for the same component.

"blocked_component_keys": ["PythonREPLComponent"]

Starter templates use a lowercase slug of the English name with underscores, such as basic_prompting.

"blocked_template_keys": ["basic_prompting"]

Providers use lowercase IDs, such as openai. GET /api/v1/model-provider-policy lists every provider the process is aware of, with provider_id and display name.

"approved_provider_ids": ["openai", "anthropic"]

Models use the identities in the model picker.

"blocked_model_keys": ["gpt-4o", "openai::gpt-4o", "openai::llm::gpt-4o"]

Integration providers use their manifest provider_id, in lowercase.

"approved_integration_provider_ids": ["google", "slack"]

Integration actions use the policy_keys a bundle declares for each capability. A key is always integrations.<provider_id>.<action>; missing provider or action segments and other malformed syntax are rejected with 422 before anything is written. Keys are trimmed and case-folded. A syntactically valid key for a provider that is not installed is accepted, so operators can configure policy before installing a bundle. This does not verify that the provider or action exists; use the loaded catalog to check the keys when the bundle is installed. GET /api/v1/integrations?include_blocked=true lists every loaded provider with the policy_keys of each of its actions, so you can copy the exact key to block.

"blocked_integration_action_keys": ["integrations.google.drive.delete"]

Blocking one key of an action blocks the whole action. A component stays visible while at least one of its actions is still allowed. At execution, an action-picker bundle declares the selected capabilities through Component.select_integration_capabilities. Blocking delete therefore does not block an invocation selecting search. Bundles that do not implement selection must be allowed to use every capability they declare. Custom toolsets that override _get_tools() or to_toolkit() must also be allowed to use every declared capability before construction, because their tools do not pass through the standard per-invocation action checks. Their connection leases cover that full set even if they implement the selection hook. An empty or undeclared action selection returns HTTP 422 with error_code: "action-unsupported"; it is not a governance denial. Blocked executions return HTTP 403 with error_code: "policy-blocked" and an administrator-action hint. Session-backed graph builds record the denial as an error message in flow history.

Change one list at a time​

Use these endpoints when you want to change only components, templates, or providers. If you are changing more than one list, use one PUT to /api/v1/policy-bundle instead. See Apply a policy.

  1. To hide or unhide components, PUT the complete blocked list to /api/v1/catalog-policy/components. A PUT replaces the whole list. Send {"blocked": []} to unblock every component.

    curl -X PUT \
    "$LANGFLOW_URL/api/v1/catalog-policy/components" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $LANGFLOW_API_KEY" \
    -d '{"blocked": ["PythonREPLComponent"]}'

    To read the current list first, GET /api/v1/catalog-policy/components.

  2. To hide or unhide starter templates, PUT the complete blocked list to /api/v1/catalog-policy/templates.

    curl -X PUT \
    "$LANGFLOW_URL/api/v1/catalog-policy/templates" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $LANGFLOW_API_KEY" \
    -d '{"blocked": ["basic_prompting"]}'

    To read the current list first, GET /api/v1/catalog-policy/templates.

  3. Optional: List the flows that include a component.

    GET /api/v1/catalog-policy/usage returns the number of flows. GET /api/v1/catalog-policy/usage/flows?component=PythonREPLComponent lists the affected flows.

  4. To allow only some providers, PUT the complete approved list to /api/v1/model-provider-policy. Send {"approved_provider_ids": []} to allow every provider.

    curl -X PUT \
    "$LANGFLOW_URL/api/v1/model-provider-policy" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $LANGFLOW_API_KEY" \
    -d '{"approved_provider_ids": ["openai", "anthropic"]}'

    Set blocked_model_keys with PUT /api/v1/policy-bundle.

  5. To see the integration policy that applies to you, GET /api/v1/integrations/policy/effective.

    It returns the caller's allowed providers and whether a plugin owns the policy instead of the bundle. Only superusers receive the operator's blocked action keys and the complete loaded provider list. Other users receive an empty blocked_action_keys list and only allowed providers in loaded_provider_ids; use GET /api/v1/integrations to list usable actions. Check unrestricted before interpreting an empty approved provider list: an externally managed empty ceiling can deny every provider. Set approved_integration_provider_ids and blocked_integration_action_keys with PUT /api/v1/policy-bundle.

Installing an integration policy plugin

An empty persisted approved_integration_provider_ids list means unrestricted. Installing a plugin must preserve that meaning. Denying all providers requires an explicit external policy configuration; a plugin changing existing policy semantics must provide an operator-visible migration or configuration step before enforcement changes. This API does not perform that migration automatically. Before enabling external enforcement, verify the intended provider set and check managed_externally and unrestricted on the effective-policy endpoint.

See also​

Was this page helpful?

Support
Search