---
title: "Create a new policy set"
method: POST
path: "/zones/{zone_id}/policy-sets"
tags: ["PolicySets"]
---

# Create a new policy set

`POST /zones/{zone_id}/policy-sets`

Creates an unbound policy set. Use updatePolicySet to bind after creating a version.

## Path parameters

- `zone_id` string, required

## Headers

- `X-API-Version` string
- `X-Client-Request-ID` string, uuid

## Request body

- PdpCreatePolicySetRequest — At most one of `target_type` or the deprecated `scope_type` may be provided; supplying both with different values returns 400. Defaults to `zone`.
  - `name` string, required
  - `scope_type` 'zone' — **Deprecated.** Use `target_type` instead. Only `zone` is accepted; use `target_type` for `user` targets.
  - `target_type` 'zone' | 'user' — What this policy set targets: - `"zone"` — applies to all requests in the zone. - `"user"` — can be bound to a specific user.

## Response `201`

The newly created policy set

- PdpPolicySetWithBinding
  - `id` string, required
  - `zone_id` string, required
  - `name` string, required
  - `scope_type` 'zone' | 'resource' | 'user' | 'session', required — **Deprecated.** Use `target_type` instead. Carries the same value.
  - `target_type` 'zone' | 'user', required — What this policy set targets: - `"zone"` — applies to all requests in the zone. - `"user"` — scoped to a specific user. `resource` and `session` are reserved; legacy sets with those scopes carry them in the deprecated `scope_type` field.
  - `latest_version_id` string, nullable
  - `latest_version` integer, nullable — Human-readable version number of the latest version (e.g., 1, 2, 3)
  - `archived_at` string, date-time, nullable
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `created_by` string, required
  - `updated_by` string, nullable
  - `owner_type` 'platform' | 'customer', required — Who manages this policy set: - `"platform"` — managed by the Keycard platform (system policies). - `"customer"` — managed by the tenant (custom policies).
  - `scope_target_id` string, nullable — **Deprecated.** Use `target_id` instead. Carries the active binding's target; null when unbound.
  - `target_id` string, nullable — Target entity ID. Equals `zone_id` for zone-targeted sets; the principal identifier for principal-scoped sets. Null only for legacy non-zone sets that predate target tracking.
  - `mode` 'active' | 'shadow', nullable
  - `active` boolean — Whether this policy set is currently bound to a scope
  - `active_version_id` string, nullable — Public ID of the currently active (bound) version
  - `active_version` integer, nullable — Human-readable version number of the active version (e.g., 1, 2, 3)
  - `shadow_version_id` string, nullable — Public ID of the shadow (observed) version, if any
  - `shadow_version` integer, nullable — Human-readable version number of the shadow version

## Other responses

- `400` — bad request error response when caller supplied invalid input data
- `401` — unauthorized error response when caller session is not authenticated
- `403` — forbidden error response when caller does not have permissions to a resource
- `409` — Conflict - a policy set with this name already exists
- `429` — rate limit exceeded error response when caller has exhausted api limits for the given time period
- `500` — internal server error response when server encountered error of its own creation
- `503` — service unavailable error when server you're attempting to reach is not available
- `default` — internal server error response when server encountered error of its own creation

---

[API](https://skmtc.net/keycardai/apis/untitled-api.md) · [All operations](https://skmtc.net/keycardai/apis/untitled-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/keycardai/untitled-api/revisions/2f5033ed4491/schema)
