---
title: "Clone a policy set into a new customer-owned set"
method: POST
path: "/zones/{zone_id}/policy-sets/{policy_set_id}/clones"
tags: ["PolicySets"]
---

# Clone a policy set into a new customer-owned set

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

## Request

- Base URL: `https://docs.keycard.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://docs.keycard.ai/zones/{zone_id}/policy-sets/{policy_set_id}/clones`
- Auth: one of:
  - HTTP bearer
  - OAuth 2
    - Client credentials
    - Token URL: `/service-account-token`

Creates a new customer-owned policy set whose first version reproduces
the manifest of a source version: the source's latest non-archived
version by default, or the one named by `source_version_id`. The set,
any copied policies and their versions, the first policy set version,
and its attestation are written in one transaction. Any failure leaves
zero writes. The source is never modified.

Platform-owned sets (for example the zone default
`default-zone-policies`) cannot be updated or versioned by customers.
Clone is how a customer forks one into a set they can edit with
`PUT .../manifest`.

`copy_policies` decides what is shared and what is duplicated
(default `platform`):

- `none`: every entry pins the same policy version as the source. No
  new policies are created.
- `platform`: platform-owned pins are copied into new customer-owned
  policies; customer-owned pins are shared. The result is fully
  editable with minimal duplication.
- `all`: every pin is copied.

Copied policies get `owner_type: customer`, the source policy's
description, and a first version whose `cedar_json`, `schema_version`
and `sha` are copied verbatim from the pinned source version. There is
no Cedar revalidation and no rewrite of the embedded static-policy
keys; the evaluator namespaces them by policy version ID.

A copied policy is named after its source, with the platform catalog
prefix `default-` stripped for platform-owned sources
(`default-user-grants` becomes `user-grants`). Customer-owned names
are kept as-is. A derived name that collides with a non-archived
policy in the zone gets the first free numeric suffix (`user-grants-2`,
`user-grants-3`, ...). Names are resolved inside the transaction; a
concurrent race still surfaces as `409 policy_name_conflict`.

Manifest order is the source's, with shared and copied pins
interleaved as they were. `schema_version` is the source version's.
`target_type` defaults to the source set's and may be overridden.

The `ETag` header is the digest of the first version's manifest, so
clone, then conditional `PUT .../manifest`, needs no intermediate GET.

Domain error codes: `policy_set_not_found`,
`policy_set_version_not_found`, `policy_set_name_conflict`,
`policy_name_conflict`, `set_archived`, `version_archived`,
`schema_version_unsupported`.

## Path parameters

- `zone_id` string, required
- `policy_set_id` string, required

## Headers

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

## Request body

- PdpClonePolicySetRequest
  - `name` string, required: Name of the new policy set. 409 `policy_set_name_conflict` on collision.
  - `target_type` 'zone' | 'user': Defaults to the source set's target type.
  - `source_version_id` string: Policy set version public ID on the source set. Defaults to the source's latest non-archived version.
  - `copy_policies` 'none' | 'platform' | 'all': Which pinned policies are duplicated as new customer-owned policies. `none` shares every pin; `platform` copies platform-owned pins and shares customer-owned ones; `all` copies every pin.

## Response `201`

The new policy set, its first version, and the per-policy outcome

- PdpClonePolicySetResult
  - `policy_set` PdpPolicySetWithBinding, required
    - `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
    - `created_by_user` PdpUser: The organization user behind a `created_by`, `updated_by` or `archived_by` value. Returned only when `expand[]=user` is requested.
      - `id` string, required: Public ID of the user in the organization's platform zone. This is not the same value as the `*_by` field it expands; use it to link to `/zones/{zone_id}/users/{id}`.
      - `zone_id` string, required: Public ID of the organization's platform zone the user belongs to.
      - `email` string, nullable, required: The user's email address, or null when not known.
    - `updated_by` string, nullable
    - `updated_by_user` PdpUser: The organization user behind a `created_by`, `updated_by` or `archived_by` value. Returned only when `expand[]=user` is requested.
      - `id` string, required: Public ID of the user in the organization's platform zone. This is not the same value as the `*_by` field it expands; use it to link to `/zones/{zone_id}/users/{id}`.
      - `zone_id` string, required: Public ID of the organization's platform zone the user belongs to.
      - `email` string, nullable, required: The user's email address, or null when not known.
    - `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
    - `policy_set_version` PdpPolicySetVersion
      - `id` string, required
      - `policy_set_id` string, required
      - `version` integer, required
      - `schema_version` string, required: Schema version pinned to this policy set version. Determines the Cedar schema used for evaluation when activated.
      - `manifest` PdpPolicySetManifest, required
        - `entries` PdpPolicySetManifestEntry[], required
          - `policy_id` string, required
          - `policy_version_id` string, required
          - `sha` string: SHA-256 of the policy version content, populated by the server
      - `manifest_sha` string, required: Hex-encoded SHA-256 of the canonicalized manifest
      - `attestation` PdpAttestationStatement: Decoded content of an Attestation JWS payload. Describes the exact policy set version composition at attestation time. This schema defines what consumers see after base64url-decoding the Attestation.payload field.
        - `v` 1, required: Statement schema version
        - `type` 'policy_set_attestation', required: Statement type discriminator
        - `zone_id` string, required
        - `policy_set_id` string, required
        - `policy_set_version` integer, required
        - `manifest_sha` string, required: SHA-256 of the policy set version manifest. Verifiers MUST check this matches the policy_set_version.manifest_sha to detect attestation/version mismatches.
        - `status` 'created' | 're_signed', required: Event that produced this attestation. "created" is the initial attestation at version creation; "re_signed" is a re-attestation after key rotation (same content, new signature).
        - `attested_at` string, date-time, required
        - `attested_by` string, required
        - `key_id` string, required: Key ID of the signing key used to produce the attestation signature. Matches the "kid" in the JWS protected header.
      - `active` boolean: Whether this policy set version is currently bound with mode='active'. Always populated in responses; clients must treat absence as unknown rather than inferring 'not bound'.
      - `archived_at` string, date-time, nullable: Timestamp when the version was archived. Non-null only for archived versions; null or absent means not archived.
      - `archived_by` string, nullable: Identifier of the actor that archived the version. Null or absent means not archived.
      - `archived_by_user` PdpUser: The organization user behind a `created_by`, `updated_by` or `archived_by` value. Returned only when `expand[]=user` is requested.
        - `id` string, required: Public ID of the user in the organization's platform zone. This is not the same value as the `*_by` field it expands; use it to link to `/zones/{zone_id}/users/{id}`.
        - `zone_id` string, required: Public ID of the organization's platform zone the user belongs to.
        - `email` string, nullable, required: The user's email address, or null when not known.
      - `created_at` string, date-time, required
      - `created_by` string, required
      - `created_by_user` PdpUser: The organization user behind a `created_by`, `updated_by` or `archived_by` value. Returned only when `expand[]=user` is requested.
        - `id` string, required: Public ID of the user in the organization's platform zone. This is not the same value as the `*_by` field it expands; use it to link to `/zones/{zone_id}/users/{id}`.
        - `zone_id` string, required: Public ID of the organization's platform zone the user belongs to.
        - `email` string, nullable, required: The user's email address, or null when not known.
      - `owner_type` 'platform' | 'customer', required: Who manages this policy set version: - `"platform"` — managed by the Keycard platform (system policy set versions). - `"customer"` — managed by the tenant (custom policy set versions).
    - `changes` PdpApplyChange[]: Per-policy outcomes, present only when created with a manifest.
      - `policy_id` string, required
      - `name` string, required: The policy's name. Lets a caller correlate a `created_policy` row with its `new_policy` request entry without a re-list.
      - `action` 'created_policy' | 'created_version' | 'reused' | 'repinned' | 'dropped', required: `repinned`: an explicit `policy_version_id` replaced a different version the set already pinned for that policy; no version minted.
      - `policy_version_id` string: Absent when action is dropped.
    - `warnings` PdpApplyWarning[]: Non-fatal findings, present only when non-empty on create.
      - `code` string, required: Machine-readable warning code, e.g. unknown_actions.
      - `message` string, required
    - `binding` PdpPolicySetBinding
      - `id` string, required: Binding identifier (stable per slot)
      - `scope_type` 'zone', required: **Deprecated.** Use `target_type` instead. Carries the same value.
      - `scope_target_id` string, required: **Deprecated.** Use `target_id` instead. Carries the same value.
      - `target_type` 'zone' | 'user', required: What this binding targets
      - `target_id` string, required: Target entity ID. Equals zone_id for zone-targeted bindings.
      - `mode` 'active' | 'shadow', required: Binding mode
      - `policy_set_id` string, required: Public ID of the bound policy set
      - `policy_set_version_id` string, required: Public ID of the bound policy set version
      - `created_at` string, date-time, required
  - `policy_set_version` PdpPolicySetVersion, required
    - `id` string, required
    - `policy_set_id` string, required
    - `version` integer, required
    - `schema_version` string, required: Schema version pinned to this policy set version. Determines the Cedar schema used for evaluation when activated.
    - `manifest` PdpPolicySetManifest, required
      - `entries` PdpPolicySetManifestEntry[], required
        - `policy_id` string, required
        - `policy_version_id` string, required
        - `sha` string: SHA-256 of the policy version content, populated by the server
    - `manifest_sha` string, required: Hex-encoded SHA-256 of the canonicalized manifest
    - `attestation` PdpAttestationStatement: Decoded content of an Attestation JWS payload. Describes the exact policy set version composition at attestation time. This schema defines what consumers see after base64url-decoding the Attestation.payload field.
      - `v` 1, required: Statement schema version
      - `type` 'policy_set_attestation', required: Statement type discriminator
      - `zone_id` string, required
      - `policy_set_id` string, required
      - `policy_set_version` integer, required
      - `manifest_sha` string, required: SHA-256 of the policy set version manifest. Verifiers MUST check this matches the policy_set_version.manifest_sha to detect attestation/version mismatches.
      - `status` 'created' | 're_signed', required: Event that produced this attestation. "created" is the initial attestation at version creation; "re_signed" is a re-attestation after key rotation (same content, new signature).
      - `attested_at` string, date-time, required
      - `attested_by` string, required
      - `key_id` string, required: Key ID of the signing key used to produce the attestation signature. Matches the "kid" in the JWS protected header.
    - `active` boolean: Whether this policy set version is currently bound with mode='active'. Always populated in responses; clients must treat absence as unknown rather than inferring 'not bound'.
    - `archived_at` string, date-time, nullable: Timestamp when the version was archived. Non-null only for archived versions; null or absent means not archived.
    - `archived_by` string, nullable: Identifier of the actor that archived the version. Null or absent means not archived.
    - `archived_by_user` PdpUser: The organization user behind a `created_by`, `updated_by` or `archived_by` value. Returned only when `expand[]=user` is requested.
      - `id` string, required: Public ID of the user in the organization's platform zone. This is not the same value as the `*_by` field it expands; use it to link to `/zones/{zone_id}/users/{id}`.
      - `zone_id` string, required: Public ID of the organization's platform zone the user belongs to.
      - `email` string, nullable, required: The user's email address, or null when not known.
    - `created_at` string, date-time, required
    - `created_by` string, required
    - `created_by_user` PdpUser: The organization user behind a `created_by`, `updated_by` or `archived_by` value. Returned only when `expand[]=user` is requested.
      - `id` string, required: Public ID of the user in the organization's platform zone. This is not the same value as the `*_by` field it expands; use it to link to `/zones/{zone_id}/users/{id}`.
      - `zone_id` string, required: Public ID of the organization's platform zone the user belongs to.
      - `email` string, nullable, required: The user's email address, or null when not known.
    - `owner_type` 'platform' | 'customer', required: Who manages this policy set version: - `"platform"` — managed by the Keycard platform (system policy set versions). - `"customer"` — managed by the tenant (custom policy set versions).
  - `policies` PdpClonedPolicyEntry[], required: One row per manifest entry, in manifest order.
    - `source_policy_id` string, required: Public ID of the policy pinned by the source version.
    - `policy_id` string, required: Public ID of the policy pinned by the clone. Equals `source_policy_id` when `shared`.
    - `policy_version_id` string, required: Public ID of the pinned policy version. Equals the source's when `shared`.
    - `name` string, required: The pinned policy's name (the derived name when `copied`).
    - `action` 'shared' | 'copied', required

## 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
- `404`: Source set not found (`policy_set_not_found`), or `source_version_id` names a version that does not exist on it (`policy_set_version_not_found`).
- `409`: `policy_set_name_conflict` (`name` already exists in the zone), `policy_name_conflict` (a derived policy name lost a race), `set_archived`, `version_archived` (the source version is archived, or it pins an archived policy version), or `schema_version_unsupported` (the source version's schema is archived; clone a newer source version).
- `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

## Changes

- **2026-10-16** `0cfaff865771`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/keycardai/apis/untitled-api/changes/zones/:zone_id/policy-sets/:policy_set_id/clones/post.md)

---

[API](https://skmtc.dev/keycardai/apis/untitled-api.md) - [All operations](https://skmtc.dev/keycardai/apis/untitled-api/llms.txt) - [OpenAPI document](https://skmtc.dev/keycardai/apis/untitled-api/revisions/0dee67e0a73a?raw)
