openapi: 3.2.0 info: title: Axonflow Dynamic Policies API version: 11.1.0 contact: name: AxonFlow Support url: https://getaxonflow.com/support license: name: Business Source License 1.1 url: https://github.com/getaxonflow/axonflow/blob/main/LICENSE description: 'Operations tagged Dynamic Policies across 4 of this provider''s published API definitions: axonflow-orchestrator-api.yaml, axonflow-policy-api.yaml, axonflow-orchestrator-openapi.yml, axonflow-policy-openapi.yml. Each path carries the servers of the definition it was published in.' servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) tags: - name: Dynamic Policies description: 'Legacy policy management on `/api/v1/policies`. Reads, tests and simulation are served; writes answer `409 LEGACY_POLICY_WRITE_FROZEN` in v11 (see the `LegacyPolicyWriteFrozen` response) - author policy through Typed Policy Authoring instead.' paths: /api/v1/policies: get: deprecated: true tags: - Dynamic Policies summary: List policies description: 'Returns a paginated list of policies with filtering support. Use this for policy management in the Customer Portal.' operationId: listPolicies parameters: - name: X-Tenant-ID in: header required: true schema: type: string - name: type in: query description: Filter by policy type schema: type: string enum: - static - dynamic - name: enabled in: query description: Filter by enabled status schema: type: boolean - name: search in: query description: Search in name and description schema: type: string - name: page in: query schema: type: integer default: 1 - name: page_size in: query schema: type: integer default: 20 maximum: 100 - name: sort_by in: query schema: type: string enum: - name - created_at - updated_at - priority - name: sort_dir in: query schema: type: string enum: - asc - desc default: asc responses: '200': description: List of policies content: application/json: schema: $ref: '#/components/schemas/PoliciesListResponse' '400': $ref: '#/components/responses/CodedBadRequest' '401': $ref: '#/components/responses/CodedUnauthorized' post: deprecated: true tags: - Dynamic Policies summary: Create policy description: Create a new policy operationId: createPolicy parameters: - name: X-Tenant-ID in: header required: true schema: type: string - name: X-User-ID in: header required: false schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePolicyRequest' responses: '201': description: Policy created content: application/json: schema: $ref: '#/components/schemas/Policy' '400': $ref: '#/components/responses/CodedBadRequest' '401': $ref: '#/components/responses/CodedUnauthorized' '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen' servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/import: post: deprecated: true tags: - Dynamic Policies summary: Import policies description: 'Bulk import policies from JSON or YAML format. Supports create or update semantics based on policy_id. On a deployment whose database connection cannot write the legacy policy tables (migrations/core/172), the request is refused `409 LEGACY_POLICY_WRITE_FROZEN` before its body is read, whatever it contains, and the refusal names the typed authoring route. An owner-role deployment still imports.' operationId: importPolicies parameters: - name: X-Tenant-ID in: header required: true schema: type: string - name: X-User-ID in: header required: false schema: type: string requestBody: required: true content: application/json: schema: type: object required: - policies properties: policies: type: array items: $ref: '#/components/schemas/CreatePolicyRequest' mode: type: string enum: - create - upsert default: upsert description: Import mode - create only or upsert (create or update) responses: '200': description: Import result content: application/json: schema: type: object properties: created: type: integer updated: type: integer failed: type: integer errors: type: array items: type: string '400': $ref: '#/components/responses/CodedBadRequest' '401': $ref: '#/components/responses/CodedUnauthorized' '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen' servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/export: get: deprecated: true tags: - Dynamic Policies summary: Export policies description: Export all policies in JSON or YAML format for backup or migration operationId: exportPolicies parameters: - name: X-Tenant-ID in: header required: true schema: type: string - name: format in: query description: Export format schema: type: string enum: - json - yaml default: json - name: type in: query description: Filter by policy type schema: type: string enum: - static - dynamic - all default: all responses: '200': description: Exported policies content: application/json: schema: type: object properties: version: type: string example: '1.0' exported_at: type: string format: date-time policies: type: array items: $ref: '#/components/schemas/Policy' application/x-yaml: schema: type: string '401': $ref: '#/components/responses/CodedUnauthorized' servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/{id}: get: deprecated: true tags: - Dynamic Policies summary: Get policy by ID operationId: getPolicy parameters: - name: id in: path required: true schema: type: string format: uuid - name: X-Tenant-ID in: header required: true schema: type: string responses: '200': description: Policy details content: application/json: schema: $ref: '#/components/schemas/Policy' '401': $ref: '#/components/responses/CodedUnauthorized' '404': description: Policy not found put: deprecated: true tags: - Dynamic Policies summary: Update policy operationId: updatePolicy parameters: - name: id in: path required: true schema: type: string format: uuid - name: X-Tenant-ID in: header required: true schema: type: string - name: X-User-ID in: header required: false schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdatePolicyRequest' responses: '200': description: Policy updated content: application/json: schema: $ref: '#/components/schemas/Policy' '400': $ref: '#/components/responses/CodedBadRequest' '401': $ref: '#/components/responses/CodedUnauthorized' '404': description: Policy not found '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen' delete: deprecated: true tags: - Dynamic Policies summary: Delete policy operationId: deletePolicy parameters: - name: id in: path required: true schema: type: string format: uuid - name: X-Tenant-ID in: header required: true schema: type: string - name: X-User-ID in: header required: false schema: type: string responses: '204': description: Policy deleted '401': $ref: '#/components/responses/CodedUnauthorized' '404': description: Policy not found '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen' servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/{id}/test: post: deprecated: true tags: - Dynamic Policies summary: Test policy against input description: Test how a specific policy evaluates against sample input operationId: testPolicyById parameters: - name: id in: path required: true schema: type: string format: uuid - name: X-Tenant-ID in: header required: true schema: type: string requestBody: required: true content: application/json: schema: type: object required: - query properties: query: type: string description: Input to evaluate context: type: object description: Additional context responses: '200': description: Test result content: application/json: schema: $ref: '#/components/schemas/PolicyEvaluationResult' '401': $ref: '#/components/responses/CodedUnauthorized' '404': description: Policy not found servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/{id}/versions: get: deprecated: true tags: - Dynamic Policies summary: Get policy version history description: 'Returns version history for a policy. Community edition limited to 5 versions.' operationId: getPolicyVersions parameters: - name: id in: path required: true schema: type: string format: uuid - name: X-Tenant-ID in: header required: true schema: type: string responses: '200': description: Version history content: application/json: schema: type: object properties: policy_id: type: string versions: type: array items: type: object properties: version: type: integer snapshot: type: object change_type: type: string changed_by: type: string changed_at: type: string format: date-time count: type: integer '401': $ref: '#/components/responses/CodedUnauthorized' '404': description: Policy not found servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/dynamic: get: deprecated: true tags: - Dynamic Policies summary: List active dynamic policies description: 'Returns the active dynamic policies visible to the CALLING tenant: the tenant''s own policies plus the shared global/default baseline. Policies owned by other tenants are never returned in the same response, and requests without a resolvable tenant are rejected with 401 (fail closed). The tenant scope is read from the `X-Tenant-ID` header. When the request arrives through the AxonFlow Agent, that header is set from the validated credential and overwrites any client-supplied value, so the caller cannot choose the scope. The orchestrator itself does not authenticate this route, so a caller with direct network access to the orchestrator can name a tenant — deploy the orchestrator on a private network behind the Agent.' operationId: listDynamicPolicies responses: '200': description: List of dynamic policies visible to the calling tenant content: application/json: schema: type: array items: $ref: '#/components/schemas/DynamicPolicy' example: - id: pol_001 name: High Risk Content Filter description: Block requests with high risk scores enabled: true conditions: - field: risk_score operator: '>' value: 0.8 actions: - type: block reason: High risk content detected - id: pol_002 name: Rate Limit Premium Users description: Apply premium rate limits enabled: true conditions: - field: user.role operator: == value: premium actions: - type: rate_limit limit: 10000 '401': description: Tenant scope could not be resolved (missing gateway-stamped X-Tenant-ID); the endpoint fails closed and returns no policy data servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/policies/test: post: deprecated: true tags: - Dynamic Policies summary: Test policy evaluation description: Test how dynamic policies evaluate a sample request operationId: testPolicy requestBody: required: true content: application/json: schema: type: object required: - query properties: query: type: string user: $ref: '#/components/schemas/UserContext' request_type: type: string example: query: SELECT * FROM customers WHERE credit_score < 500 user: id: 123 email: analyst@company.com role: analyst request_type: sql responses: '200': description: Policy evaluation result content: application/json: schema: $ref: '#/components/schemas/PolicyEvaluationResult' example: allowed: true applied_policies: - sql-injection-filter - pii-protection risk_score: 0.25 required_actions: [] processing_time_ms: 3 servers: - url: https://orchestrator.getaxonflow.com description: Production (SaaS) - url: http://localhost:8081 description: Local Development /api/v1/dynamic-policies: get: deprecated: true tags: - Dynamic Policies summary: List dynamic policies description: 'Retrieve a paginated list of the caller''s policies, whatever their category: with no `category` parameter no category filter is applied, so rows categorised otherwise, or not at all (written through `/api/v1/policies`), are returned too. A `category` parameter must start with `dynamic-` or `media-`, and any other value is refused `400`. When `type=media` is passed without a category, results are filtered to `media-*` categories. `limit` is the preferred pagination parameter; `page_size` is deprecated but still accepted.' operationId: getApiV1DynamicPolicies parameters: - $ref: '#/components/parameters/TenantID' - name: type in: query description: Filter by policy type schema: $ref: '#/components/schemas/PolicyType' - name: category in: query description: 'Filter by category. Must start with `dynamic-` or `media-` (e.g., dynamic-risk, media-safety); other values are rejected with a validation error. ' schema: type: string - name: enabled in: query description: Filter by enabled status schema: type: boolean - name: search in: query description: Search in policy name and description schema: type: string maxLength: 100 - name: page in: query description: Page number (1-indexed) schema: type: integer minimum: 1 default: 1 - name: limit in: query description: Items per page (preferred over the deprecated page_size) schema: type: integer minimum: 1 maximum: 100 default: 20 - name: page_size in: query deprecated: true description: Items per page (deprecated - use limit instead) schema: type: integer minimum: 1 maximum: 100 - name: sort_by in: query description: Sort field schema: type: string enum: - name - created_at - updated_at - priority default: created_at - name: sort_dir in: query description: Sort direction schema: type: string enum: - asc - desc default: desc responses: '200': description: List of dynamic policies content: application/json: schema: $ref: '#/components/schemas/PoliciesListResponse_2' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' x-operation-id-source: normalized x-operation-id-original: listDynamicPolicies post: deprecated: true tags: - Dynamic Policies summary: Create a dynamic policy description: 'Create a new dynamic policy. `category` is required and must start with `dynamic-` or `media-` (e.g., dynamic-risk, media-safety).' operationId: createDynamicPolicy parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/UserID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePolicyRequest_2' responses: '201': description: Dynamic policy created content: application/json: schema: $ref: '#/components/schemas/PolicyResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '403': description: Tier validation failed (e.g., organization-tier policy without Enterprise license) content: application/json: schema: $ref: '#/components/schemas/APIError' '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen_2' '500': $ref: '#/components/responses/InternalError' servers: - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) - url: http://localhost:8081 description: Orchestrator direct (internal only) /api/v1/dynamic-policies/import: post: deprecated: true tags: - Dynamic Policies summary: Import dynamic policies description: 'Bulk import dynamic policies from JSON. Supports up to 100 policies per request. Every policy must have a category starting with `dynamic-` or `media-`. On a deployment whose database connection cannot write the legacy policy tables (migrations/core/172), the request is refused `409 LEGACY_POLICY_WRITE_FROZEN` before its body is read, whatever it contains, and the refusal names the typed authoring route. An owner-role deployment still imports.' operationId: importDynamicPolicies parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/UserID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ImportPoliciesRequest' responses: '200': description: Import results content: application/json: schema: $ref: '#/components/schemas/ImportPoliciesResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen_2' '500': $ref: '#/components/responses/InternalError' servers: - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) - url: http://localhost:8081 description: Orchestrator direct (internal only) /api/v1/dynamic-policies/export: get: deprecated: true tags: - Dynamic Policies summary: Export dynamic policies description: 'Export the organization''s policies as JSON: every row the list route returns for the caller, whatever its category, including a row with no category (one written through `/api/v1/policies`). The same set as `/api/v1/policies/export`. Before v11.1.0 only rows with a `dynamic-*` or `media-*` category were included, and the rest were left out without notice (#4293). This family''s import still accepts only `dynamic-*` and `media-*` rows and refuses the whole batch at the first other one, so a file that carries other rows is imported through `/api/v1/policies/import`, which accepts them.' operationId: exportDynamicPolicies parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Exported dynamic policies content: application/json: schema: $ref: '#/components/schemas/ExportPoliciesResponse' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' servers: - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) - url: http://localhost:8081 description: Orchestrator direct (internal only) /api/v1/dynamic-policies/effective: get: deprecated: true tags: - Dynamic Policies summary: Get effective dynamic policies description: 'Returns the enabled dynamic policies for the tenant (both `dynamic-*` and `media-*` categories), sorted by priority ascending. Returns up to 100 policies.' operationId: getEffectiveDynamicPolicies parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Effective dynamic policies content: application/json: schema: $ref: '#/components/schemas/PoliciesListResponse_2' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' servers: - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) - url: http://localhost:8081 description: Orchestrator direct (internal only) /api/v1/dynamic-policies/{id}: parameters: - $ref: '#/components/parameters/PolicyID' - $ref: '#/components/parameters/TenantID' get: deprecated: true tags: - Dynamic Policies summary: Get a dynamic policy description: 'Retrieve a single dynamic policy by ID. Returns 404 if the policy exists but is not a dynamic policy (category not `dynamic-*`/`media-*`).' operationId: getDynamicPolicy responses: '200': description: Dynamic policy details content: application/json: schema: $ref: '#/components/schemas/PolicyResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' put: deprecated: true tags: - Dynamic Policies summary: Update a dynamic policy description: 'Update an existing dynamic policy. Only provided fields are updated. If `category` is changed, the new value must still start with `dynamic-` or `media-`.' operationId: updateDynamicPolicy parameters: - $ref: '#/components/parameters/UserID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdatePolicyRequest_2' responses: '200': description: Dynamic policy updated content: application/json: schema: $ref: '#/components/schemas/PolicyResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '403': description: Tier validation failed content: application/json: schema: $ref: '#/components/schemas/APIError' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen_2' '500': $ref: '#/components/responses/InternalError' delete: deprecated: true tags: - Dynamic Policies summary: Delete a dynamic policy description: Soft-delete a dynamic policy. The policy is marked as deleted but retained for audit purposes. operationId: deleteDynamicPolicy parameters: - $ref: '#/components/parameters/UserID' responses: '204': description: Dynamic policy deleted '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '403': description: Tier validation failed content: application/json: schema: $ref: '#/components/schemas/APIError' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/LegacyPolicyWriteFrozen_2' '500': $ref: '#/components/responses/InternalError' servers: - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) - url: http://localhost:8081 description: Orchestrator direct (internal only) /api/v1/dynamic-policies/{id}/versions: parameters: - $ref: '#/components/parameters/PolicyID' - $ref: '#/components/parameters/TenantID' get: deprecated: true tags: - Dynamic Policies summary: Get dynamic policy version history description: Retrieve the complete version history of a dynamic policy for audit purposes operationId: getDynamicPolicyVersions responses: '200': description: Version history content: application/json: schema: $ref: '#/components/schemas/PolicyVersionResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' servers: - url: https://agent.getaxonflow.com description: Production (Agent single entry point, ADR-024) - url: http://localhost:8080 description: Local development (Agent single entry point) - url: http://localhost:8081 description: Orchestrator direct (internal only) components: schemas: PolicyEvaluationResult: type: object description: 'The policy verdict carried on every orchestrator response. Its property SET is held equal to the Go type''s JSON members by `TestThePublishedSchemasMatchTheTypesThePlatformMarshals`, which compares the two by reflection, so a field added to one and not the other fails CI rather than reaching a spec-generated client (#3724). The properties are also written in the Go type''s declaration order as a courtesy to a reader diffing the two; nothing enforces that, and order is not part of the contract. ' properties: allowed: type: boolean applied_policies: type: array items: type: string risk_score: type: number minimum: 0 maximum: 1 severity: type: string enum: - critical - high - medium - low description: Highest severity among the matched policies. severity_policy_id: type: string description: The policy that contributed `severity`. required_actions: type: array items: type: string processing_time_ms: type: integer database_accessed: type: boolean evaluation_error: type: boolean description: 'Distinguishes **could not govern** from **a policy said block**, and is the only signal that does. True when the engine could NOT complete evaluation because governance-segment resolution failed (a resolver or storage error -- never "the caller belongs to zero segments"). `allowed` is always false when this is set, because the engine fails CLOSED on that error, so a consumer reading only `allowed` still behaves safely; a consumer that audits or alerts MUST read this field to tell an availability failure apart from a genuine policy match. Before it existed the only signal was the magic string `applied_policies: ["segment_resolution_failed"]`. ' segments_resolved: type: boolean description: 'True only when a resolved, non-empty governance-segment set was actually factored into this verdict. False covers every legitimate organisation-only case -- no identity supplied, no resolver wired (community, or no SCIM), or the caller belongs to zero segments -- as well as the `evaluation_error` case. None of those are failures: the flag exists so a reader of a policy-simulation preview does not mistake a legitimate org-only allow for a segment-aware one. ' applied_policies_detail: type: array description: 'Structured mirror of `applied_policies` carrying each matched policy''s risk level and allow_override metadata, without a second query. No session override is applied to a result in v11 (#4252). ' items: $ref: '#/components/schemas/AppliedPolicyDetail' preferred_provider: type: string description: 'LLM provider a matched routing policy prefers. When more than one applying route row names one, the LAST applying row in evaluation order wins it and the routing reason: rows are walked by priority, highest first, then newest first, so the winner is the lowest-priority applying row (#4249). ' allowed_providers: type: array items: type: string description: 'Strict provider allow-list for compliance routing. Failover stays within this list. It is the intersection of every applying route row''s list, whatever their order; an empty intersection refuses the request (`no_compliant_provider`). ' routing_reason: type: string description: Why routing was changed. DynamicPolicy: type: object properties: id: type: string name: type: string description: type: string enabled: type: boolean type: type: string description: 'Policy type — narrower-than-category dimension surfaced on list/CRUD responses (e.g. risk, content, user, cost). ' example: risk category: type: string description: 'Policy category. Dynamic policies use a `dynamic-` prefix (dynamic-risk, dynamic-compliance, dynamic-security, dynamic-cost, dynamic-access). ' example: dynamic-risk tier: type: string enum: - system - organization - tenant description: 'Policy tier. Dynamic policies default to `tenant`; organization-tier policies require Enterprise. ' priority: type: integer description: 'Evaluation priority — lower values evaluate first. Useful when multiple dynamic policies match the same request. ' example: 0 created_at: type: string format: date-time description: When the dynamic policy was first inserted. updated_at: type: string format: date-time description: When the dynamic policy was last modified. conditions: type: array items: type: object properties: field: type: string operator: type: string enum: - == - '!=' - '>' - < - '>=' - <= - contains - matches value: description: Comparison value actions: type: array items: type: object properties: type: type: string enum: - block - allow - rate_limit - redact - alert reason: type: string limit: type: integer AppliedPolicyDetail: type: object description: 'One structured per-policy match inside `PolicyEvaluationResult`. ' properties: policy_id: type: string policy_name: type: string description: type: string action: type: string risk_level: type: string enum: - low - medium - high - critical allow_override: type: boolean description: False if and only if the policy forbids a session override. matched_rule: type: string segment_id: type: string description: 'The governance segment this policy is scoped to, or absent when it is not segment-scoped. ATTRIBUTION AND AUDIT ONLY -- it is not an override-eligibility signal anywhere: a segment-scoped policy uses the same `allow_override` contract as a tenant policy. ' CreatePolicyRequest: type: object required: - name - type properties: name: type: string description: type: string type: type: string enum: - static - dynamic category: type: string tier: type: string enum: - organization - tenant default: tenant pattern: type: string description: Required for static policies action: type: string enum: - block - require_approval - redact - warn - log severity: type: string enum: - critical - high - medium - low priority: type: integer default: 100 enabled: type: boolean default: true conditions: type: array items: type: object description: Required for dynamic policies actions: type: array items: type: object description: Required for dynamic policies PoliciesListResponse: type: object properties: policies: type: array items: $ref: '#/components/schemas/Policy' pagination: type: object properties: page: type: integer page_size: type: integer total_items: type: integer total_pages: type: integer CodedErrorResponse: type: object description: 'The CODED error envelope: `{error: {code, message}}`, where `code` is a screaming-snake string enum. This is what the per-handler `writeError` methods emit across the policy API, the LLM provider API, the agents, template, unified-execution and media-governance APIs, and every handler in the RBI module (362 call sites in total). It is one of TWO error SHAPES this document describes. `code` is a STRING on this envelope; it is never an HTTP status integer. `LLMProviderAPIError` is this shape with the `code` enum constrained to the five values the LLM-provider handlers emit. ' properties: error: type: object properties: code: type: string description: Machine-readable error code, screaming snake case. example: NOT_FOUND message: type: string required: - code - message required: - error UserContext: type: object properties: id: type: integer email: type: string role: type: string region: type: string description: User's region, read by geo-based routing policies. permissions: type: array items: type: string tenant_id: type: string org_id: type: string description: Organisation for multi-tenant isolation, populated from the X-Org-ID header the agent stamps on the trusted hop. Policy: type: object description: A policy with full metadata properties: id: type: string format: uuid policy_id: type: string description: Human-readable identifier name: type: string description: type: string type: type: string enum: - static - dynamic category: type: string tier: type: string enum: - system - organization - tenant pattern: type: string description: Regex pattern (for static policies) action: type: string enum: - block - require_approval - redact - warn - log severity: type: string enum: - critical - high - medium - low priority: type: integer enabled: type: boolean conditions: type: array items: type: object description: Conditions (for dynamic policies) actions: type: array items: type: object description: Actions (for dynamic policies) version: type: integer tenant_id: type: string organization_id: type: string description: 'The organisation that owns this policy. This is the current wire field name and it is not deprecated: it is unrelated to a database column of the same name that was retired in an earlier release. The two share a name and nothing else. ' created_by: type: string created_at: type: string format: date-time updated_by: type: string updated_at: type: string format: date-time UpdatePolicyRequest: type: object description: All fields optional for partial update properties: name: type: string description: type: string pattern: type: string action: type: string enum: - block - require_approval - redact - warn - log severity: type: string enum: - critical - high - medium - low priority: type: integer enabled: type: boolean conditions: type: array items: type: object actions: type: array items: type: object PolicyResource: type: object properties: id: type: string description: Unique policy identifier example: pol_abc123def456 name: type: string description: Human-readable policy name minLength: 3 maxLength: 100 example: Block PII Access description: type: string description: Detailed policy description maxLength: 500 example: Prevent unauthorized access to personally identifiable information type: $ref: '#/components/schemas/PolicyType' category: type: string description: Policy category (dynamic-risk, dynamic-compliance, media-safety, etc.) example: dynamic-risk tier: type: string enum: - system - organization - tenant description: Policy tier in the hierarchy (system policies are immutable) conditions: type: array items: $ref: '#/components/schemas/PolicyCondition' minItems: 1 description: Conditions that must all match (AND logic) actions: type: array items: $ref: '#/components/schemas/PolicyAction' minItems: 1 description: Actions to execute when policy matches priority: type: integer minimum: 0 maximum: 1000 default: 0 description: Higher priority policies are evaluated first enabled: type: boolean default: true description: Whether the policy is active version: type: integer minimum: 1 description: Policy version number, incremented on each update tenant_id: type: string description: Owning tenant ID organization_id: type: string description: 'The organisation that owns this policy. Since #3490 the organisation is what SELECTS a policy row, at every tier -- this is not an organization-tier-only field, and the previous wording described the retired organization_id COLUMN rather than this resource field. ' tags: type: array items: type: string description: Tags for categorization created_at: type: string format: date-time description: Creation timestamp updated_at: type: string format: date-time description: Last update timestamp created_by: type: string description: User who created the policy updated_by: type: string description: User who last updated the policy deleted_at: type: string format: date-time description: Soft-delete timestamp (present only on deleted policies) PolicyVersionResponse: type: object properties: versions: type: array items: type: object properties: version: type: integer snapshot: $ref: '#/components/schemas/PolicyResource' changed_by: type: string changed_at: type: string format: date-time change_type: type: string enum: - create - update - enable - disable - delete change_summary: type: string ImportPoliciesResponse: type: object properties: created: type: integer description: Number of policies created updated: type: integer description: Number of policies updated skipped: type: integer description: Number of policies skipped errors: type: array items: type: string description: Error messages for failed imports PolicyResponse: type: object properties: policy: $ref: '#/components/schemas/PolicyResource' PaginationMeta: type: object description: Pagination metadata properties: page: type: integer description: Current page number example: 1 page_size: type: integer description: Items per page example: 20 total_items: type: integer description: Total number of items example: 45 total_pages: type: integer description: Total number of pages example: 3 ExportPoliciesResponse: type: object properties: policies: type: array items: $ref: '#/components/schemas/PolicyResource' exported_at: type: string format: date-time tenant_id: type: string PoliciesListResponse_2: type: object properties: policies: type: array items: $ref: '#/components/schemas/PolicyResource' pagination: $ref: '#/components/schemas/PaginationMeta' ImportPoliciesRequest: type: object required: - policies properties: policies: type: array items: $ref: '#/components/schemas/CreatePolicyRequest_2' minItems: 1 maxItems: 100 overwrite_mode: type: string enum: - skip - overwrite - error default: skip description: How to handle existing policies PolicyAction: type: object required: - type properties: type: $ref: '#/components/schemas/ActionType' config: type: object additionalProperties: true description: Action-specific configuration example: message: Request blocked by policy channel: security-alerts PolicyType: type: string enum: - content - user - risk - cost - context_aware - media - rate-limit - budget - time-access - role-access - mcp - connector description: 'Policy type determines evaluation context: - `content`: Evaluates request/response content - `user`: Evaluates user attributes - `risk`: Evaluates risk scores - `cost`: Evaluates cost estimates - `context_aware`: Context-aware controls (tenant isolation, debug restriction, sensitive-data control) - `media`: Media governance policies (multimodal image governance) - `rate-limit`, `budget`, `time-access`: MCP rate/budget controls - `role-access`, `mcp`, `connector`: MCP access controls ' CreatePolicyRequest_2: type: object required: - name - type - conditions - actions properties: name: type: string minLength: 3 maxLength: 100 description: type: string maxLength: 500 type: $ref: '#/components/schemas/PolicyType' category: type: string description: 'Policy category (dynamic-risk, dynamic-compliance, media-safety, etc.). Required on the /api/v1/dynamic-policies surface, where it must start with `dynamic-` or `media-`. ' tier: type: string enum: - organization - tenant description: Policy tier. Only organization or tenant is allowed via the API. conditions: type: array items: $ref: '#/components/schemas/PolicyCondition' minItems: 1 actions: type: array items: $ref: '#/components/schemas/PolicyAction' minItems: 1 priority: type: integer minimum: 0 maximum: 1000 default: 0 enabled: type: boolean default: true tags: type: array items: type: string description: Tags for categorization PolicyCondition: type: object required: - field - operator - value properties: field: type: string description: 'Field to evaluate. `media.*` fields apply to media governance policies (multimodal image governance); `step.*` fields are retry-aware workflow step fields for WCP policies. ' enum: - query - response - user.email - user.role - user.department - user.tenant_id - risk_score - request_type - connector - cost_estimate - media.has_faces - media.face_count - media.has_biometric_data - media.nsfw_score - media.violence_score - media.content_safe - media.document_type - media.is_sensitive_document - media.has_pii - media.pii_types - media.has_extracted_text - media.extracted_text_length - step.gate_count - step.completion_count - step.prior_completion_status - step.prior_output_available - step.last_decision - step.first_attempt_age_seconds - step.idempotency_key operator: $ref: '#/components/schemas/ConditionOperator' value: oneOf: - type: string - type: number - type: boolean - type: array items: type: string description: Value to compare against APIError: type: object properties: error: type: object properties: code: type: string description: Error code message: type: string description: Human-readable error message details: type: array items: type: object properties: field: type: string message: type: string description: Field-level validation errors ActionType: type: string enum: - block - require_approval - redact - warn - alert - log - route - modify_risk description: 'Action to take when policy matches: - `block`: Block the request with message - `require_approval`: Hold the request for human approval (HITL) - `redact`: Redact sensitive content from response - `warn`: Allow the request but attach a warning - `alert`: Send alert to configured channel - `log`: Log to audit trail - `route`: Route to specific provider - `modify_risk`: Adjust risk score ' ConditionOperator: type: string enum: - equals - not_equals - contains - not_contains - contains_any - regex - greater_than - less_than - in - not_in description: Comparison operator for conditions UpdatePolicyRequest_2: type: object properties: name: type: string minLength: 3 maxLength: 100 description: type: string maxLength: 500 type: $ref: '#/components/schemas/PolicyType' category: type: string description: Policy category. Only changeable on non-system policies. conditions: type: array items: $ref: '#/components/schemas/PolicyCondition' actions: type: array items: $ref: '#/components/schemas/PolicyAction' priority: type: integer minimum: 0 maximum: 1000 enabled: type: boolean tags: type: array items: type: string description: Tags for categorization PoliciesListResponse_3: type: object properties: policies: type: array items: $ref: '#/components/schemas/PolicyResource' pagination: $ref: '#/components/schemas/PaginationMeta' ImportPoliciesRequest_2: type: object required: - policies properties: policies: type: array items: $ref: '#/components/schemas/CreatePolicyRequest_3' minItems: 1 maxItems: 100 overwrite_mode: type: string enum: - skip - overwrite - error default: skip description: How to handle existing policies CreatePolicyRequest_3: type: object required: - name - type - conditions - actions properties: name: type: string minLength: 3 maxLength: 100 description: type: string maxLength: 500 type: $ref: '#/components/schemas/PolicyType' category: type: string description: 'Policy category (dynamic-risk, dynamic-compliance, media-safety, etc.). Required on the /api/v1/dynamic-policies surface, where it must start with `dynamic-` or `media-`. ' tier: type: string enum: - organization - tenant description: Policy tier. Only organization or tenant is allowed via the API. conditions: type: array items: $ref: '#/components/schemas/PolicyCondition' minItems: 1 actions: type: array items: $ref: '#/components/schemas/PolicyAction' minItems: 1 priority: type: integer minimum: 0 maximum: 1000 default: 0 enabled: type: boolean default: true tags: type: array items: type: string description: Tags for categorization UpdatePolicyRequest_3: type: object properties: name: type: string minLength: 3 maxLength: 100 description: type: string maxLength: 500 type: $ref: '#/components/schemas/PolicyType' category: type: string description: Policy category. Only changeable on non-system policies. conditions: type: array items: $ref: '#/components/schemas/PolicyCondition' actions: type: array items: $ref: '#/components/schemas/PolicyAction' priority: type: integer minimum: 0 maximum: 1000 enabled: type: boolean tags: type: array items: type: string description: Tags for categorization responses: CodedUnauthorized: description: Unauthorized - missing or invalid organization scope (coded envelope) content: application/json: schema: $ref: '#/components/schemas/CodedErrorResponse' example: error: code: UNAUTHORIZED message: Organization ID required LegacyPolicyWriteFrozen: description: 'The legacy policy tables are read-only. migrations/core/172 revoked INSERT/UPDATE/DELETE/TRUNCATE on static_policies and dynamic_policies from the application roles, and this endpoint writes them, so the write is refused permanently rather than transiently. It is not an entitlement fact: no licence, edition or upgrade changes it. Author policies through the typed authoring route instead. Reads on this endpoint are unaffected. A deployment whose connection may still write the tables (the database owner) does not receive this response. Where the connection cannot write them, the create, update and bulk-import routes answer this before reading the request body, whatever it contains. ' content: application/json: schema: $ref: '#/components/schemas/CodedErrorResponse' example: error: code: LEGACY_POLICY_WRITE_FROZEN message: 'The legacy policy tables are read-only in v11: migrations/core/172 revoked write access from the application role, and this endpoint writes them. Author policies through the typed authoring route at /api/v1/typed-policies instead. Reads on this endpoint are unaffected.' CodedBadRequest: description: Invalid request (coded envelope) content: application/json: schema: $ref: '#/components/schemas/CodedErrorResponse' example: error: code: INVALID_INPUT message: connector_name is required LegacyPolicyWriteFrozen_2: description: 'The legacy policy tables are read-only. `migrations/core/172` revoked INSERT, UPDATE, DELETE and TRUNCATE on `static_policies` and `dynamic_policies` from the application roles, and this endpoint writes them, so the write is refused permanently rather than transiently. It is not an entitlement fact: no licence, edition or upgrade changes it. Author policies through the typed authoring route (`/api/v1/typed-policies`, in `orchestrator-api.yaml`) instead. Reads on this endpoint are unaffected. A deployment whose connection may still write the tables (the database owner; a property of the connection, not of `AXONFLOW_DB_USE_APP_ROLE` alone) is not bound by the revoke and does not receive this response. Where the connection cannot write them, the create, update and bulk-import routes answer this before reading the request body, whatever it contains. ' content: application/json: schema: $ref: '#/components/schemas/APIError' example: error: code: LEGACY_POLICY_WRITE_FROZEN message: 'The legacy policy tables are read-only in v11: migrations/core/172 revoked write access from the application role, and this endpoint writes them. Author policies through the typed authoring route at /api/v1/typed-policies instead. Reads on this endpoint are unaffected.' ValidationError: description: Request validation failed content: application/json: schema: $ref: '#/components/schemas/APIError' example: error: code: VALIDATION_ERROR message: Request validation failed details: - field: name message: Name must be between 3 and 100 characters - field: conditions[0].operator message: 'Invalid operator: like. Must be one of: equals, contains, regex' InternalError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/APIError' example: error: code: INTERNAL_ERROR message: An unexpected error occurred Unauthorized: description: Missing or invalid tenant ID content: application/json: schema: $ref: '#/components/schemas/APIError' example: error: code: UNAUTHORIZED message: Missing tenant ID NotFound: description: Policy not found content: application/json: schema: $ref: '#/components/schemas/APIError' example: error: code: NOT_FOUND message: Policy not found LegacyPolicyWriteFrozen_3: description: 'The legacy policy tables are read-only. `migrations/core/172` revoked INSERT, UPDATE, DELETE and TRUNCATE on `static_policies` and `dynamic_policies` from the application roles, and this endpoint writes them, so the write is refused permanently rather than transiently. It is not an entitlement fact: no licence, edition or upgrade changes it. Author policies through the typed authoring route (`/api/v1/typed-policies`, in `orchestrator-api.yaml`) instead. Reads on this endpoint are unaffected. A deployment whose connection may still write the tables (the database owner; a property of the connection, not of `AXONFLOW_DB_USE_APP_ROLE` alone) is not bound by the revoke and does not receive this response. Where the connection cannot write them, the create, update and bulk-import routes answer this before reading the request body, whatever it contains. ' content: application/json: schema: $ref: '#/components/schemas/APIError' example: error: code: LEGACY_POLICY_WRITE_FROZEN message: 'The legacy policy tables are read-only in v11: migrations/core/172 revoked write access from the application role, and this endpoint writes them. Author policies through the typed authoring route at /api/v1/typed-policies instead. Reads on this endpoint are unaffected.' parameters: UserID: name: X-User-ID in: header required: false description: User identifier for audit logging schema: type: string example: user@company.com PolicyID: name: id in: path required: true description: Policy unique identifier schema: type: string example: pol_abc123def456 TenantID: name: X-Tenant-ID in: header required: true description: 'Tenant identifier for multi-tenancy isolation. When calling through the Agent (recommended), this header is stamped automatically from the authenticated client — you do not set it yourself. Required only on direct Orchestrator calls (internal deployments). ' schema: type: string example: tenant_abc123 securitySchemes: basicAuth: type: http scheme: basic description: OAuth2-style client credentials (clientId:clientSecret) BearerAuth: type: http scheme: bearer bearerFormat: JWT description: Enterprise JWT token (see /scripts/generate-jwt.sh) x-refined-from: - axonflow-orchestrator-api.yaml - axonflow-policy-api.yaml - axonflow-orchestrator-openapi.yml - axonflow-policy-openapi.yml