openapi: "3.1.0"

info:
  title: Docker AI Governance Policy API
  version: "1"
  description: |
    HTTP+JSON API for managing Docker governance policies and rules.

    **Resource model.** An organization owns one or more policies. Each policy
    contains a list of rules grouped into a single domain: either `network` or
    `filesystem`. A policy's domain is derived from its rule actions; mixing
    domains within a single policy is not permitted.

    **Lifecycle.** Create a policy with CreatePolicy, then add rules with
    CreateRule. Rules can be updated in place with UpdateRule or removed with
    DeleteRule. Deleting all rules does not delete the policy itself.

    **Rule evaluation.** All rules in a policy are tested against every request.
    `deny` always wins: if any rule matches with `decision: deny`, the request
    is denied regardless of any `allow` rules.

    **Enforcement.** Organization policies take precedence over local sandbox
    policies and cannot be overridden by individual users.

    **Propagation.** Policy changes take up to five minutes to reach developer
    machines after being written.

    See https://docs.docker.com/ai/sandboxes/governance/ for product
    documentation.
  contact:
    name: Docker
    url: https://www.docker.com/products/ai-governance/

tags:
  - name: policies
    description: Policy lifecycle management
  - name: rules
    description: Rule management within an allowlist policy

servers:
  - url: https://hub.docker.com/v2

security:
  - bearerAuth: []

paths:
  /orgs/{org_name}/governance/policies:
    parameters:
      - $ref: "#/components/parameters/OrgName"
    get:
      operationId: listPolicies
      tags: [policies]
      summary: List policies
      description: >
        Returns a shallow summary of all policies for the org.
        The rule set is not included; use GetPolicy to fetch the full object.
      responses:
        "200":
          description: Array of policy summaries. Rule sets are not included; use GetPolicy to fetch a full policy.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PolicySummary"
              examples:
                default:
                  value:
                    - id: pol_06evsmp24r1pg71cm8500546pkbn
                      name: "Security Research — hardened"
                      org: my-org
                      scope:
                        profiles: [security]
                      created_at: "2026-04-22T00:00:00Z"
                      updated_at: "2026-04-22T00:00:00Z"
                      type: allowlist_v0
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createPolicy
      tags: [policies]
      summary: Create policy
      description: >
        Creates a new policy with an empty rule set. Rules are added separately
        via the rules sub-resource.
      requestBody:
        description: Policy name and optional scope.
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePolicyRequest"
            examples:
              default:
                value:
                  name: "Security Research — hardened"
                  scope:
                    profiles: [security]
      responses:
        "201":
          description: Policy created. Returns the new policy without its rule set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Policy"
              examples:
                default:
                  value:
                    id: pol_06evsmp24r1pg71cm8500546pkbn
                    name: "Security Research — hardened"
                    org: my-org
                    scope:
                      profiles: [security]
                    created_at: "2026-04-22T00:00:00Z"
                    updated_at: "2026-04-22T00:00:00Z"
        "400":
          $ref: "#/components/responses/InvalidArgument"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/Conflict"
        "500":
          $ref: "#/components/responses/InternalError"

  /orgs/{org_name}/governance/policies/{policy_id}:
    parameters:
      - $ref: "#/components/parameters/OrgName"
      - $ref: "#/components/parameters/PolicyID"
    get:
      operationId: getPolicy
      tags: [policies]
      summary: Get policy
      description: Returns the full policy including its `allowlist_v0` rule set.
      responses:
        "200":
          description: Full policy including its `allowlist_v0` rule set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Policy"
              examples:
                default:
                  value:
                    id: pol_06evsmp24r1pg71cm8500546pkbn
                    name: "Security Research — hardened"
                    org: my-org
                    scope:
                      profiles: [security]
                    created_at: "2026-04-22T00:00:00Z"
                    updated_at: "2026-04-22T00:00:00Z"
                    allowlist_v0:
                      domain: network
                      rules:
                        - id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
                          name: allow research mirrors
                          actions: [connect:tcp, connect:udp]
                          resources: [research.mitre.org, cve.mitre.org]
                          decision: allow
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /orgs/{org_name}/governance/policies/{policy_id}/rules:
    parameters:
      - $ref: "#/components/parameters/OrgName"
      - $ref: "#/components/parameters/PolicyID"
    post:
      operationId: createRule
      tags: [rules]
      summary: Create rule
      description: |
        Adds a rule to the policy's rule set. All rules in a policy must share
        the same domain (network or filesystem); mixing domains is rejected.

        **Network** actions: `connect:tcp`, `connect:udp`. Resources are
        hostnames (for example, `example.com`), wildcard subdomains (`*.example.com`
        for one level, `**.example.com` for any depth), hostnames with an optional
        port (for example, `example.com:443`), or CIDRs in IPv4 or IPv6 notation
        (for example, `10.0.0.0/8` or `2001:db8::/32`).

        **Filesystem** actions: `read`, `write`. Resources are paths (for example,
        `/data`). Use `*` to match within a single path segment and `**` to match
        recursively across segments (for example, `/data/**`).

        Changes may take up to five minutes to reach developer machines.
      requestBody:
        description: Rule definition including actions, resources, and decision.
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRuleRequest"
            examples:
              network:
                summary: Network rule
                value:
                  name: allow research mirrors
                  actions: [connect:tcp, connect:udp]
                  resources: [research.mitre.org, cve.mitre.org]
                  decision: allow
              filesystem:
                summary: Filesystem rule
                value:
                  name: allow data directory
                  actions: [read, write]
                  resources: [/data]
                  decision: allow
      responses:
        "201":
          description: Rule created and added to the policy's rule set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Rule"
              examples:
                network:
                  summary: Network rule
                  value:
                    id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
                    name: allow research mirrors
                    actions: [connect:tcp, connect:udp]
                    resources: [research.mitre.org, cve.mitre.org]
                    decision: allow
                filesystem:
                  summary: Filesystem rule
                  value:
                    id: rule_07fwtnr0kn2qetl1b9olfbyz8kob
                    name: allow data directory
                    actions: [read, write]
                    resources: [/data]
                    decision: allow
        "400":
          $ref: "#/components/responses/InvalidArgument"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /orgs/{org_name}/governance/policies/{policy_id}/rules/{rule_id}:
    parameters:
      - $ref: "#/components/parameters/OrgName"
      - $ref: "#/components/parameters/PolicyID"
      - $ref: "#/components/parameters/RuleID"
    patch:
      operationId: updateRule
      tags: [rules]
      summary: Update rule
      description: |
        Partially updates a rule using JSON Merge Patch semantics (RFC 7396).
        Only fields present in the request body are updated; absent fields are
        left unchanged. Returns the rule in both its old and new states.

        Changing `actions` across domains (for example, from network actions to
        filesystem actions) is rejected. Changes may take up to five minutes to
        reach developer machines.
      requestBody:
        description: Fields to update. Absent fields are left unchanged.
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: "#/components/schemas/UpdateRuleRequest"
            examples:
              default:
                value:
                  resources: ["research.mitre.org"]
      responses:
        "200":
          description: Rule updated, returns old and new states.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpdateRuleResponse"
              examples:
                default:
                  value:
                    old:
                      id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
                      name: allow research mirrors
                      actions: [connect:tcp, connect:udp]
                      resources: [research.mitre.org, cve.mitre.org]
                      decision: allow
                    new:
                      id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
                      name: allow research mirrors
                      actions: [connect:tcp, connect:udp]
                      resources: [research.mitre.org]
                      decision: allow
        "400":
          $ref: "#/components/responses/InvalidArgument"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteRule
      tags: [rules]
      summary: Delete rule
      description: |
        Deletes a rule from the policy. Returns the deleted rule. Changes may
        take up to five minutes to reach developer machines.
      responses:
        "200":
          description: Rule deleted, returns the deleted rule.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteRuleResponse"
              examples:
                default:
                  value:
                    deleted:
                      id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
                      name: allow research mirrors
                      actions: [connect:tcp, connect:udp]
                      resources: [research.mitre.org, cve.mitre.org]
                      decision: allow
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Short-lived JWT obtained by exchanging Docker Hub credentials at
        `POST https://hub.docker.com/v2/users/login`. Pass the JWT in the
        `Authorization: Bearer <token>` header. Tokens expire after a short
        period; request a fresh one when you receive a `401`.

        The `password` field of the login request accepts any of the following
        credential types:

        | Type | Format | Notes |
        |------|--------|-------|
        | Password | Plain text | Your Docker Hub account password. |
        | Personal Access Token (PAT) | `dckr_pat_*` | Recommended over passwords. Create one under Account Settings → Security. |
        | Organization Access Token (OAT) | `dckr_oat_*` | Scoped to an organization. Create one under Organization Settings → Access Tokens. |

        PAT and OAT strings can't be used directly as a bearer token. They must
        be exchanged at the login endpoint first.

        See [Docker Hub authentication](https://docs.docker.com/reference/api/hub/latest/#tag/authentication-api/operation/AuthCreateAccessToken)
        for full details.

  parameters:
    OrgName:
      name: org_name
      in: path
      required: true
      description: Docker Hub organization name.
      schema:
        type: string
      examples:
        default:
          value: my-org

    PolicyID:
      name: policy_id
      in: path
      required: true
      description: Unique policy identifier.
      schema:
        type: string
      examples:
        default:
          value: pol_06evsmp24r1pg71cm8500546pkbn

    RuleID:
      name: rule_id
      in: path
      required: true
      description: Unique rule identifier within the policy.
      schema:
        type: string
      examples:
        default:
          value: rule_06evsm9qjm1pdsk0a8nkfaxy7jna

  schemas:
    PolicySummary:
      type: object
      description: Shallow policy representation returned by ListPolicies. Excludes the rule set.
      required: [id, name, org, scope, created_at, updated_at, type]
      properties:
        id:
          type: string
          examples:
            - pol_06evsmp24r1pg71cm8500546pkbn
        name:
          type: string
          description: Human-readable label, unique within the organization.
          examples:
            - "Security Research — hardened"
        org:
          type: string
          examples:
            - my-org
        scope:
          $ref: "#/components/schemas/Scope"
        created_at:
          type: string
          format: date-time
          examples:
            - "2026-04-22T00:00:00Z"
        updated_at:
          type: string
          format: date-time
          examples:
            - "2026-04-22T00:00:00Z"
        type:
          type: string
          description: >
            Identifies the rule-set format. Always `allowlist_v0`, corresponding
            to the `allowlist_v0` property on the full Policy object.
          examples:
            - allowlist_v0

    Policy:
      type: object
      description: Full policy representation including the allowlist rule set.
      required: [id, name, org, scope, created_at, updated_at]
      properties:
        id:
          type: string
          examples:
            - pol_06evsmp24r1pg71cm8500546pkbn
        name:
          type: string
          description: Human-readable label, unique within the organization.
          examples:
            - "Security Research — hardened"
        org:
          type: string
          examples:
            - my-org
        scope:
          $ref: "#/components/schemas/Scope"
        created_at:
          type: string
          format: date-time
          examples:
            - "2026-04-22T00:00:00Z"
        updated_at:
          type: string
          format: date-time
          examples:
            - "2026-04-22T00:00:00Z"
        allowlist_v0:
          $ref: "#/components/schemas/AllowlistV0"

    Scope:
      type: object
      description: Restricts the policy to specific profiles or teams. Empty or absent lists mean the policy applies org-wide.
      properties:
        profiles:
          type: array
          items:
            type: string
          examples:
            - ["security"]
        teams:
          type: array
          items:
            type: string

    AllowlistV0:
      type: object
      description: |
        Network or filesystem allowlist containing a list of rules. Present on
        Policy when `PolicySummary.type` is `allowlist_v0`; omitted when the
        policy has no rules yet. All rules in an allowlist share the same domain.
        All rules are evaluated on every request: `deny` always wins over `allow`.
      required: [rules]
      properties:
        domain:
          type: string
          description: >
            The access-control domain shared by all rules in this allowlist.
            Derived from rule actions: network actions (`connect:tcp`,
            `connect:udp`) produce `network`; filesystem actions (`read`,
            `write`) produce `filesystem`. Present when `rules` is non-empty;
            absent when the allowlist has no rules.
          enum: [network, filesystem]
          examples:
            - network
        rules:
          type: array
          items:
            $ref: "#/components/schemas/Rule"

    Rule:
      type: object
      description: A single allow or deny rule within an allowlist policy.
      required: [id, name, actions, resources, decision]
      properties:
        id:
          type: string
          examples:
            - rule_06evsm9qjm1pdsk0a8nkfaxy7jna
        name:
          type: string
          description: Human-readable label for the rule.
          examples:
            - allow research mirrors
        actions:
          $ref: "#/components/schemas/RuleActions"
        resources:
          $ref: "#/components/schemas/RuleResources"
        decision:
          $ref: "#/components/schemas/RuleDecision"

    CreatePolicyRequest:
      type: object
      description: Fields required to create a new policy.
      required: [name]
      properties:
        name:
          type: string
          description: Policy name, unique within the organization.
          examples:
            - "Security Research — hardened"
        scope:
          $ref: "#/components/schemas/Scope"

    CreateRuleRequest:
      type: object
      description: Fields required to create a new rule within a policy's rule set.
      required: [name, actions, resources, decision]
      properties:
        name:
          type: string
          description: Human-readable label for the rule.
          examples:
            - allow research mirrors
        actions:
          $ref: "#/components/schemas/RuleActions"
        resources:
          $ref: "#/components/schemas/RuleResources"
        decision:
          $ref: "#/components/schemas/RuleDecision"

    UpdateRuleRequest:
      type: object
      description: JSON Merge Patch (RFC 7396). Only present fields are updated.
      properties:
        name:
          type: string
          description: Human-readable label for the rule.
          examples:
            - allow research mirrors
        actions:
          $ref: "#/components/schemas/RuleActions"
        resources:
          $ref: "#/components/schemas/RuleResources"
        decision:
          $ref: "#/components/schemas/RuleDecision"

    UpdateRuleResponse:
      type: object
      description: The rule state before and after the update.
      required: [old, new]
      properties:
        old:
          $ref: "#/components/schemas/Rule"
        new:
          $ref: "#/components/schemas/Rule"

    DeleteRuleResponse:
      type: object
      description: The deleted rule.
      required: [deleted]
      properties:
        deleted:
          $ref: "#/components/schemas/Rule"

    RuleActions:
      type: array
      items:
        type: string
        enum: [connect:tcp, connect:udp, read, write]
      minItems: 1
      description: >
        Network actions: `connect:tcp`, `connect:udp`.
        Filesystem actions: `read`, `write`.
        All actions in a rule must belong to the same domain; mixing network
        and filesystem actions in one rule is rejected.
      examples:
        - ["connect:tcp", "connect:udp"]

    RuleResources:
      type: array
      items:
        type: string
      minItems: 1
      description: >
        Network domain: hostnames (for example, `example.com`), wildcard
        subdomains (`*.example.com` or `**.example.com`), hostnames with port
        (for example, `example.com:443`), or CIDRs in IPv4 or IPv6 notation
        (for example, `10.0.0.0/8` or `2001:db8::/32`). Filesystem domain:
        paths (for example, `/data`); `*` matches within one path segment,
        `**` matches recursively (for example, `/data/**`).
      examples:
        - ["research.mitre.org", "cve.mitre.org"]

    RuleDecision:
      type: string
      enum: [allow, deny]
      description: >
        Outcome applied when this rule matches a request. `deny` always
        wins: if any rule in the policy matches with `decision: deny`, the
        request is denied even if other rules match with `decision: allow`.
      examples:
        - allow

    Error:
      type: object
      description: Error envelope returned on all non-2xx responses.
      required: [error]
      properties:
        error:
          type: object
          description: Error detail.
          required: [code, message]
          examples:
            - code: not_found
              message: policy not found
          properties:
            code:
              type: string
              description: >
                Machine-readable error code. `not_found`: the requested resource
                does not exist. `conflict`: a resource with the same name already
                exists. `invalid_argument`: the request body is malformed or
                fails validation. `unauthenticated`: missing or invalid
                credentials. `permission_denied`: the caller lacks the required
                permission, or the org is not entitled to use governance.
                `unimplemented`: the endpoint or feature is not yet available.
                `internal`: unexpected server error.
              enum:
                - not_found
                - conflict
                - invalid_argument
                - unauthenticated
                - permission_denied
                - unimplemented
                - internal
            message:
              type: string

  responses:
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            default:
              value:
                error:
                  code: not_found
                  message: policy not found

    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            default:
              value:
                error:
                  code: conflict
                  message: policy name already in use

    InvalidArgument:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            default:
              value:
                error:
                  code: invalid_argument
                  message: "name is required"

    Unauthenticated:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            default:
              value:
                error:
                  code: unauthenticated
                  message: unauthenticated

    PermissionDenied:
      description: >
        Caller lacks the required permission for this org, or the org is not
        entitled to use governance.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            default:
              value:
                error:
                  code: permission_denied
                  message: permission denied

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            default:
              value:
                error:
                  code: internal
                  message: internal error
