> ## Documentation Index
> Fetch the complete documentation index at: https://docs.barndoor.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Policy

> Create a policy governing exactly one MCP server.

A rule grants its `actions` to its `roles_groups`. Access is denied by default, so a
tool no rule grants is already blocked — `authorized: false` is only for carving an
exception out of a broader allow.

Write ONE rule per `roles_groups` set — all of a principal's actions on one rule,
with `:approve` carrying allow-vs-confirm. Example, reads free and writes confirmed:

    {"name": "Support tooling", "mcp_server_id": "3f7c…", "rules": [
      {"name": "Support issue access", "authorized": true, "active": true,
       "actions": ["tools/call:list_issues", "tools/call:create_issue:approve"],
       "roles_groups": ["group:Support"]}]}

One policy per (server, agent scope): a second ACTIVE policy over the same
`mcp_server_id` and `application_ids` is refused with 409 naming the holder. Add rules
to the existing policy instead, or scope the new one to specific `application_ids`.
`validate_policy` reports this before you write.



## OpenAPI

````yaml /api-reference/openapi.yaml post /api/policy/v2/policies
openapi: 3.1.0
info:
  title: Barndoor.ai Public API
  description: Unified public API specification for Barndoor.ai platform
  version: 2.18.0
servers:
  - url: https://{host}
    description: Barndoor public API
    variables:
      host:
        default: platform.barndoor.ai
        description: >-
          The host serving your Barndoor deployment. Defaults to production;
          override it for another environment or a dedicated deployment.
security:
  - ApiKey: []
  - OAuth2:
      - openid
      - profile
      - email
tags:
  - name: dlp
    x-group: Data Control Center
  - name: identity
    x-group: Identity
  - name: llm-gateway
    x-group: LLM Gateway
  - name: notification
    x-group: Notifications
  - name: policy
    x-group: Policies
  - name: registry
    x-group: MCP Gateway
  - name: system-management
    x-group: Log Exports
paths:
  /api/policy/v2/policies:
    post:
      tags:
        - policy
      summary: Create Policy
      description: >-
        Create a policy governing exactly one MCP server.


        A rule grants its `actions` to its `roles_groups`. Access is denied by
        default, so a

        tool no rule grants is already blocked — `authorized: false` is only for
        carving an

        exception out of a broader allow.


        Write ONE rule per `roles_groups` set — all of a principal's actions on
        one rule,

        with `:approve` carrying allow-vs-confirm. Example, reads free and
        writes confirmed:

            {"name": "Support tooling", "mcp_server_id": "3f7c…", "rules": [
              {"name": "Support issue access", "authorized": true, "active": true,
               "actions": ["tools/call:list_issues", "tools/call:create_issue:approve"],
               "roles_groups": ["group:Support"]}]}

        One policy per (server, agent scope): a second ACTIVE policy over the
        same

        `mcp_server_id` and `application_ids` is refused with 409 naming the
        holder. Add rules

        to the existing policy instead, or scope the new one to specific
        `application_ids`.

        `validate_policy` reports this before you write.
      operationId: create_policy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePolicy'
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyDetailResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
components:
  schemas:
    CreatePolicy:
      properties:
        name:
          type: string
          title: Name
          description: >-
            Human-friendly policy name, unique within an organization. Required
            when creating.
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
          description: Longer description of the policy.
        support_contact:
          anyOf:
            - type: string
            - type: 'null'
          title: Support Contact
          description: Primary point of contact associated with the policy.
        tags:
          anyOf:
            - type: array
              items:
                type: string
            - type: 'null'
          title: Tags
          description: Tags for categorizing the policy.
        status:
          anyOf:
            - $ref: '#/components/schemas/PolicyStatus'
            - type: 'null'
          description: The current lifecycle status of the policy.
        application_ids:
          anyOf:
            - type: array
              items:
                type: string
            - type: 'null'
          title: Application Ids
          description: >-
            AI agents this policy applies to, as application UUIDs. An empty or
            omitted list means the policy applies to ALL agents — it must never
            contain an empty string. Retrieve real ids from `GET
            /api/registry/v1/agents`.
        rules:
          anyOf:
            - items:
                $ref: '#/components/schemas/PolicyRule-Input'
              type: array
            - type: 'null'
          title: Rules
          description: >-
            Full set of rules that govern the policy. This REPLACES the existing
            rule list on update — send the complete set, not a delta. Anything
            no rule grants is denied. Write ONE rule per distinct `roles_groups`
            set: put every action for a principal on a single rule and let the
            `:approve` suffix carry allow-vs-confirm, rather than splitting
            reads and writes across two rules naming the same principal.
        mcp_server_id:
          type: string
          title: Mcp Server Id
          description: >-
            UUID of the MCP server this policy governs. Immutable after creation
            — rules reference server-specific tool actions, so target a
            different server by creating a new policy rather than editing this
            one. Retrieve real ids from `GET /api/registry/v1/servers`.
      type: object
      required:
        - name
        - mcp_server_id
      title: CreatePolicy
      description: Request body for creating a new policy.
    PolicyDetailResponse:
      properties:
        policy:
          $ref: '#/components/schemas/PolicyDetail'
      type: object
      required:
        - policy
      title: PolicyDetailResponse
      description: Envelope for detail responses.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    PolicyStatus:
      type: string
      enum:
        - DRAFT
        - ACTIVE
        - INACTIVE
        - ARCHIVED
      title: PolicyStatus
      description: >-
        Policy lifecycle status.


        - ``DRAFT``: authored but never enforced. Safe to build up
        incrementally.

        - ``ACTIVE``: enforcing on live tool calls.

        - ``INACTIVE``: previously active, now suspended. Rules are retained but
        not enforced.

        - ``ARCHIVED``: retired. Kept for audit history; not enforced and not
        offered for reuse.


        A newly created policy defaults to ``DRAFT``; set ``ACTIVE`` explicitly
        to begin enforcing.
    PolicyRule-Input:
      properties:
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: >-
            Optional display name for the rule. Letters, digits, spaces, hyphens
            and underscores only — a colon or other punctuation is rejected with
            a 422, which is easy to hit because rule names tend to read like
            `Security: view agents`. Names must also be unique within the policy
            once punctuation is normalized, so `My Rule` and `My-Rule` collide.
          default: ''
          examples:
            - Engineering read access
            - Support write with approval
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
          description: >-
            Optional human-readable description for the rule; has no effect on
            enforcement
        authorized:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Authorized
          description: >-
            Effect of the rule: True grants the listed `actions`, False denies
            them. Access is denied by default, so a tool that no rule grants is
            already blocked — an explicit False rule is only needed to carve an
            exception out of a broader allow. The effective model is tri-state
            rather than binary: Always Allow is True on the plain action, Needs
            Approval is True on the `:approve` twin (see `actions`), and Blocked
            is simply the absence of a grant.
          default: false
        active:
          type: boolean
          title: Active
          description: >-
            Whether this rule is enforced. False keeps the rule on the policy
            but omits it from enforcement — use it to disable a rule without
            deleting it.
          default: true
        actions:
          items:
            type: string
          type: array
          title: Actions
          description: >-
            Tool actions this rule matches, in the wire format
            `tools/call:<tool_name>` — for example `tools/call:create_issue`.
            Use `*` to match every action on the server. Tool names are specific
            to the target MCP server: enumerate the server's real tools rather
            than guessing names. A trailing `:approve` selects the
            Needs-Approval variant of a tool
            (`tools/call:create_issue:approve`), which requires per-call human
            confirmation; the plain action is Always Allow. Do not grant both
            twins of the same tool in one rule.
          examples:
            - - tools/call:create_issue
            - - tools/call:delete_page:approve
            - - '*'
        roles_groups:
          items:
            type: string
          type: array
          title: Roles Groups
          description: >-
            Principals this rule applies to, as PREFIXED entries:
            `role:<role_name>` for an IdP/Keycloak role, `group:<group_name>`
            for a user group, or the literal `*` for all users (the default). A
            bare name with no `role:`/`group:` prefix matches NOTHING. Retrieve
            the organization's real role and group names from `GET
            /api/identity/public/v1/users/roles-groups`. A principal set should
            appear on at most ONE rule in a policy — collect all of its actions
            there instead of spreading them over several rules.
          examples:
            - - '*'
            - - group:Engineering
            - - role:admin
              - group:Platform
        condition:
          anyOf:
            - $ref: '#/components/schemas/PolicyRuleCondition-Input'
            - type: 'null'
          description: >-
            Optional CEL condition; the rule applies only when it evaluates
            true. Shape is `{"match": <matcher>}`. Omit entirely for an
            unconditional rule.
      type: object
      required:
        - actions
      title: PolicyRule
      description: >-
        A single rule: which principals may take which tool actions, optionally
        conditioned.
    PolicyDetail:
      properties:
        id:
          type: string
          format: uuid
          title: Id
        organization_id:
          type: string
          title: Organization Id
        name:
          type: string
          title: Name
        status:
          type: string
          title: Status
        support_contact:
          anyOf:
            - type: string
            - type: 'null'
          title: Support Contact
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        mcp_server_id:
          type: string
          title: Mcp Server Id
        application_ids:
          items:
            type: string
          type: array
          title: Application Ids
        tags:
          items:
            type: string
          type: array
          title: Tags
        rules:
          items:
            $ref: '#/components/schemas/PolicyRule-Output'
          type: array
          title: Rules
        created_at:
          type: string
          format: date-time
          title: Created At
        updated_at:
          type: string
          format: date-time
          title: Updated At
        created_by_user_id:
          type: string
          title: Created By User Id
        updated_by_user_id:
          type: string
          title: Updated By User Id
      type: object
      required:
        - id
        - organization_id
        - name
        - status
        - mcp_server_id
        - created_at
        - updated_at
        - created_by_user_id
        - updated_by_user_id
      title: PolicyDetail
      description: Detailed representation of a policy including rules.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    PolicyRuleCondition-Input:
      properties:
        match:
          oneOf:
            - $ref: '#/components/schemas/Expression'
            - $ref: '#/components/schemas/OperatorAll-Input'
            - $ref: '#/components/schemas/OperatorAny-Input'
            - $ref: '#/components/schemas/OperatorNone-Input'
          title: Match
          description: >-
            Matcher tree. Either a single expression `{"expr": "..."}` or a
            boolean combinator `{"all"|"any"|"none": {"of": [<matcher>, ...]}}`,
            which nest arbitrarily. The variant is selected by WHICH KEY IS
            PRESENT — there is no separate discriminator field. Example: {"all":
            {"of": [{"expr": "P.attr.department == 'Engineering'"}, {"expr":
            "R.attr.env != 'prod'"}]}}
      type: object
      required:
        - match
      title: PolicyRuleCondition
    PolicyRule-Output:
      properties:
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: >-
            Optional display name for the rule. Letters, digits, spaces, hyphens
            and underscores only — a colon or other punctuation is rejected with
            a 422, which is easy to hit because rule names tend to read like
            `Security: view agents`. Names must also be unique within the policy
            once punctuation is normalized, so `My Rule` and `My-Rule` collide.
          default: ''
          examples:
            - Engineering read access
            - Support write with approval
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
          description: >-
            Optional human-readable description for the rule; has no effect on
            enforcement
        authorized:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Authorized
          description: >-
            Effect of the rule: True grants the listed `actions`, False denies
            them. Access is denied by default, so a tool that no rule grants is
            already blocked — an explicit False rule is only needed to carve an
            exception out of a broader allow. The effective model is tri-state
            rather than binary: Always Allow is True on the plain action, Needs
            Approval is True on the `:approve` twin (see `actions`), and Blocked
            is simply the absence of a grant.
          default: false
        active:
          type: boolean
          title: Active
          description: >-
            Whether this rule is enforced. False keeps the rule on the policy
            but omits it from enforcement — use it to disable a rule without
            deleting it.
          default: true
        actions:
          items:
            type: string
          type: array
          title: Actions
          description: >-
            Tool actions this rule matches, in the wire format
            `tools/call:<tool_name>` — for example `tools/call:create_issue`.
            Use `*` to match every action on the server. Tool names are specific
            to the target MCP server: enumerate the server's real tools rather
            than guessing names. A trailing `:approve` selects the
            Needs-Approval variant of a tool
            (`tools/call:create_issue:approve`), which requires per-call human
            confirmation; the plain action is Always Allow. Do not grant both
            twins of the same tool in one rule.
          examples:
            - - tools/call:create_issue
            - - tools/call:delete_page:approve
            - - '*'
        roles_groups:
          items:
            type: string
          type: array
          title: Roles Groups
          description: >-
            Principals this rule applies to, as PREFIXED entries:
            `role:<role_name>` for an IdP/Keycloak role, `group:<group_name>`
            for a user group, or the literal `*` for all users (the default). A
            bare name with no `role:`/`group:` prefix matches NOTHING. Retrieve
            the organization's real role and group names from `GET
            /api/identity/public/v1/users/roles-groups`. A principal set should
            appear on at most ONE rule in a policy — collect all of its actions
            there instead of spreading them over several rules.
          examples:
            - - '*'
            - - group:Engineering
            - - role:admin
              - group:Platform
        condition:
          anyOf:
            - $ref: '#/components/schemas/PolicyRuleCondition-Output'
            - type: 'null'
          description: >-
            Optional CEL condition; the rule applies only when it evaluates
            true. Shape is `{"match": <matcher>}`. Omit entirely for an
            unconditional rule.
      type: object
      required:
        - actions
      title: PolicyRule
      description: >-
        A single rule: which principals may take which tool actions, optionally
        conditioned.
    Expression:
      properties:
        expr:
          type: string
          title: Expr
          description: >-
            A single CEL expression that must evaluate to a boolean. Top-level
            variables: C, G, P, R, V, constants, globals, request, runtime,
            variables. `P` is the calling principal and `R` the resource (the
            tool call being authorized). Commonly used attributes:
            `P.attr.groups` and `P.attr.roles` hold the UNPREFIXED role and
            group names from the user's profile — note the contrast with a
            rule's `roles_groups`, whose entries are `role:`/`group:`-prefixed;
            other IdP profile fields such as `P.attr.department` are available;
            and `R.attr.<argument>` exposes the tool call's own arguments.
            Expressions are compiled at write time and rejected if invalid or if
            they reference an unknown variable.
          examples:
            - P.attr.department == 'Engineering'
            - R.attr.repository.startsWith('barndoor-ai/')
      type: object
      required:
        - expr
      title: Expression
    OperatorAll-Input:
      properties:
        all:
          $ref: '#/components/schemas/ConditionOf-Input'
          description: Logical AND — every matcher in `of` must evaluate true.
      type: object
      required:
        - all
      title: OperatorAll
    OperatorAny-Input:
      properties:
        any:
          $ref: '#/components/schemas/ConditionOf-Input'
          description: Logical OR — at least one matcher in `of` must evaluate true.
      type: object
      required:
        - any
      title: OperatorAny
    OperatorNone-Input:
      properties:
        none:
          $ref: '#/components/schemas/ConditionOf-Input'
          description: >-
            Logical NOR — the rule applies only if NO matcher in `of` evaluates
            true.
      type: object
      required:
        - none
      title: OperatorNone
    PolicyRuleCondition-Output:
      properties:
        match:
          oneOf:
            - $ref: '#/components/schemas/Expression'
            - $ref: '#/components/schemas/OperatorAll-Output'
            - $ref: '#/components/schemas/OperatorAny-Output'
            - $ref: '#/components/schemas/OperatorNone-Output'
          title: Match
          description: >-
            Matcher tree. Either a single expression `{"expr": "..."}` or a
            boolean combinator `{"all"|"any"|"none": {"of": [<matcher>, ...]}}`,
            which nest arbitrarily. The variant is selected by WHICH KEY IS
            PRESENT — there is no separate discriminator field. Example: {"all":
            {"of": [{"expr": "P.attr.department == 'Engineering'"}, {"expr":
            "R.attr.env != 'prod'"}]}}
      type: object
      required:
        - match
      title: PolicyRuleCondition
    ConditionOf-Input:
      properties:
        of:
          items:
            oneOf:
              - $ref: '#/components/schemas/Expression'
              - $ref: '#/components/schemas/OperatorAll-Input'
              - $ref: '#/components/schemas/OperatorAny-Input'
              - $ref: '#/components/schemas/OperatorNone-Input'
          type: array
          title: Of
          description: >-
            Operand list for the enclosing `all`/`any`/`none` combinator. Each
            operand is itself a matcher — either `{"expr": "..."}` or a further
            nested combinator.
      type: object
      required:
        - of
      title: ConditionOf
    OperatorAll-Output:
      properties:
        all:
          $ref: '#/components/schemas/ConditionOf-Output'
          description: Logical AND — every matcher in `of` must evaluate true.
      type: object
      required:
        - all
      title: OperatorAll
    OperatorAny-Output:
      properties:
        any:
          $ref: '#/components/schemas/ConditionOf-Output'
          description: Logical OR — at least one matcher in `of` must evaluate true.
      type: object
      required:
        - any
      title: OperatorAny
    OperatorNone-Output:
      properties:
        none:
          $ref: '#/components/schemas/ConditionOf-Output'
          description: >-
            Logical NOR — the rule applies only if NO matcher in `of` evaluates
            true.
      type: object
      required:
        - none
      title: OperatorNone
    ConditionOf-Output:
      properties:
        of:
          items:
            oneOf:
              - $ref: '#/components/schemas/Expression'
              - $ref: '#/components/schemas/OperatorAll-Output'
              - $ref: '#/components/schemas/OperatorAny-Output'
              - $ref: '#/components/schemas/OperatorNone-Output'
          type: array
          title: Of
          description: >-
            Operand list for the enclosing `all`/`any`/`none` combinator. Each
            operand is itself a matcher — either `{"expr": "..."}` or a further
            nested combinator.
      type: object
      required:
        - of
      title: ConditionOf
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: >-
        A Barndoor API key, sent as `Authorization: Bearer <key>`. Keys are
        prefixed `bdai_` and are opaque — do not attempt to decode one.
        Interchangeable with an OAuth 2.0 access token.
    OAuth2:
      type: oauth2
      x-issuer: https://auth.barndoor.ai/realms/barndoor
      description: >-
        An OAuth 2.0 access token from Barndoor's identity provider, sent as
        `Authorization: Bearer <token>`. Public clients must use PKCE.
        Interchangeable with an API key.
      flows:
        authorizationCode:
          authorizationUrl: >-
            https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/auth
          tokenUrl: >-
            https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/token
          scopes:
            openid: Issue an OIDC ID token identifying the caller.
            profile: Read the caller's basic profile claims.
            email: Read the caller's email address.
            offline_access: Issue a refresh token. Optional — no operation requires it.
        clientCredentials:
          tokenUrl: >-
            https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/token
          scopes:
            openid: Issue an OIDC ID token identifying the caller.
            profile: Read the caller's basic profile claims.
            email: Read the caller's email address.
            offline_access: Issue a refresh token. Optional — no operation requires it.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.