---
title: "Create or resume a policy set draft"
method: POST
path: "/zones/{zone_id}/policy-sets/{policy_set_id}/draft"
tags: ["PolicySetDrafts"]
---

# Create or resume a policy set draft

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

## 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}/draft`
- Auth: one of:
  - HTTP bearer
  - OAuth 2
    - Client credentials
    - Token URL: `/service-account-token`

Safe draft creation that never overwrites an existing draft. If a draft already exists for this policy set, it is returned as-is (200 OK). Three body modes control how a new draft is initialized: (1) empty body — hydrate from the latest non-archived version, (2) { base_version_id } — hydrate from a specific version, (3) full body with manifest and schema_version — create with explicit content. base_version_id and manifest are mutually exclusive; providing both is rejected.

## Path parameters

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

## Headers

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

## Request body

- PdpCreatePolicySetDraftRequest: Optional body for POST draft creation. Three modes: (1) empty/omitted — hydrate from latest version, (2) only base_version_id — hydrate from that version, (3) manifest provided — use explicit content. base_version_id and manifest are mutually exclusive; providing both is rejected.
  - `base_version_id` string: ID of a policy set version to hydrate from. Mutually exclusive with manifest — the server rejects requests containing both fields.
  - `manifest` PdpPolicySetManifest
    - `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
  - `schema_version` string
  - `name` string
  - `description` string

## Response `200`

Existing draft resumed (unchanged)

- PdpPolicySetDraft
  - `policy_set_id` string, required
  - `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
  - `schema_version` string, required
  - `name` string, nullable
  - `description` string, nullable
  - `base_version_id` string, nullable: ID of the policy set version this draft was hydrated from. Null when the draft was created without an existing version.
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `updated_by` string, required
  - `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.
  - `warnings` PdpPolicySetDraftManifestWarning[]: Warnings about manifest entries that would prevent creating a version from this draft. Present only when there are warnings; omitted when empty.
    - `policy_id` string, required
    - `policy_version_id` string, required
    - `type` 'policy_version_archived' | 'schema_version_mismatch', required
    - `message` string, required: Human-readable description of the warning, e.g. 'validated against schema "2026-02-24", draft targets "2026-03-16"' or 'policy version is archived'.
    - `detail` PdpManifestWarningDetail: Additional structured context for a manifest warning. The shape depends on the warning type.
      - `policy_schema_version` string: Schema version the policy version was validated against. Present only for schema_version_mismatch warnings.
      - `draft_schema_version` string: Schema version the draft targets. Present only for schema_version_mismatch warnings.

## Other responses

- `201`: New draft created
- `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`: not found error response when caller does not have permission to see a resource or the resource does not exist
- `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`: 18 breaking, 12 warning, 84 info
  - the response property `details/items/field` became optional for the status `400`
  - the response property `details/items/field` became optional for the status `401`
  - the response property `details/items/field` became optional for the status `403`
  - the response property `details/items/field` became optional for the status `404`
  - …110 more
- **2026-04-20** `932699a5ea16`: 2 info
  - added the optional property `warnings` to the response with the `200` status
  - added the optional property `warnings` to the response with the `201` status
- **2026-04-02** `aee6e66de0d3`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/keycardai/apis/untitled-api/changes/zones/:zone_id/policy-sets/:policy_set_id/draft/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)
