---
title: "Create an incomplete draft envelope"
method: POST
path: "/envelopes/drafts"
tags: ["Envelopes"]
---

# Create an incomplete draft envelope

`POST /envelopes/drafts`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/envelopes/drafts`
- Auth: HTTP bearer

Creates a versioned draft from a ready unused document. Signers and fields may both be empty. The response is the canonical editable snapshot and includes computed readiness. The existing createEnvelope operation remains the one-shot compatibility path. Honors Idempotency-Key.

## Headers

- `Idempotency-Key` string

## Request body

- DraftEnvelopeCreateRequest
  - `documentId` string, required
  - `subject` string
  - `message` string, nullable
  - `signingOrder` 'parallel' | 'sequential'
  - `emailVerification` boolean
  - `expiresInDays` integer
  - `signers` NewDraftSignerInput[]
    - `key` string, required: Request-scoped key fields use in assignee.signerId.
    - `name` string, required
    - `email` string, email, required
    - `role` 'signer' | 'approver' | 'reviewer': What the recipient is asked to do. `signer` executes the document and must be assigned at least one field. `approver` approves or declines and may hold fields. `reviewer` acknowledges having read it and may hold none. Defaults to `signer`.
    - `phone` string, nullable: Optional valid international phone number. Common punctuation is accepted and the stored value is normalized to E.164.
    - `order` integer, required: 1-based routing position; normalized to a dense sequence.
  - `fields` NewDraftFieldInput[]
    - `type` 'signature' | 'initials' | 'text' | 'date' | 'checkbox' | 'number' | 'email' | 'select' | 'radio' | 'textarea', required: How the field is filled and rendered. Values are stored as strings; signature and initials use images.
    - `assignee` union, required: `sender` for a sender-owned static field, or an object identifying a signer in this request.
      - 'sender'
      - object
        - `signerId` string, required
    - `name` string, nullable: Stable field name, unique within the envelope when present.
    - `value` string, nullable: Sender value or editable signer default. Empty string and null are empty values. Textarea values are limited to 10,000 characters; every other scalar field value is limited to 1,000.
    - `readonly` boolean: Only valid for sender-owned fields. Sender fields are always static in signer sessions.
    - `validation` object, nullable
      - `minLength` integer
      - `maxLength` integer
      - `pattern` string: Safe JavaScript regular expression without flags.
      - `min` number
      - `max` number
      - `minDate` string, date
      - `maxDate` string, date
    - `options` object[], nullable: Required for select/radio and forbidden for other types. Option values must be unique.
      - `value` string, required
      - `label` string, required
    - `pageIndex` integer, required: 0-based page, and it must be inside the document's page count.
    - `xRatio` number, required: Left edge as a fraction of page width, from the left.
    - `yRatio` number, required: Top edge as a fraction of page height, from the top.
    - `widthRatio` number, required: Width as a fraction of page width. `xRatio + widthRatio` must stay within the page.
    - `heightRatio` number, required: Height as a fraction of page height. `yRatio + heightRatio` must stay within the page.
    - `required` boolean: Whether the signer must fill this field before the envelope can complete.
    - `label` string, nullable: Shown to the signer beside the field. Most useful on `text` and `date`, where the field alone does not say what is wanted.
    - `detectionSource` 'agent' | 'manual' | 'agent-then-manual': Where the placement came from. Pass through what field detection returned so the provenance survives; omit it for hand-placed fields.
    - `detectionConfidence` number, nullable: The detector's confidence, when the placement came from detection.
    - `detectionReason` string, nullable: Why the detector placed the field here.
  - `cc` CcRecipientInput[]: Copy recipients, delivered a read-only link to the finalized PDF once the envelope is executed and nothing before it.
    - `name` string, required
    - `email` string, email, required

## Response `201`

The new editable draft at editor version 1.

- object
  - `data` DraftEnvelope, required
    - `id` string, required
    - `status` 'draft', required
    - `editorVersion` integer, required
    - `document` object, required
      - `id` string, required
      - `filename` string, required
      - `pageCount` integer, required
    - `subject` string, required
    - `message` string, nullable, required
    - `signingOrder` 'parallel' | 'sequential', required
    - `emailVerification` boolean, required
    - `expiresAt` string, date-time, required
    - `signers` DraftSigner[], required
      - `id` string, required
      - `key` string, required: Equals id and can be used in the next replacement.
      - `name` string, required
      - `email` string, email, required
      - `role` 'signer' | 'approver' | 'reviewer', required: What the recipient is asked to do. `signer` executes the document and must be assigned at least one field. `approver` approves or declines and may hold fields. `reviewer` acknowledges having read it and may hold none. Defaults to `signer`.
      - `phone` string, nullable, required
      - `order` integer, required
    - `fields` DraftField[], required
      - `type` 'signature' | 'initials' | 'text' | 'date' | 'checkbox' | 'number' | 'email' | 'select' | 'radio' | 'textarea', required: How the field is filled and rendered. Values are stored as strings; signature and initials use images.
      - `assignee` union, required: `sender` for a sender-owned static field, or an object identifying a signer in this request.
        - 'sender'
        - object
          - `signerId` string, required
      - `name` string, nullable, required: Stable field name, unique within the envelope when present.
      - `value` string, nullable, required: Sender value or editable signer default. Empty string and null are empty values. Textarea values are limited to 10,000 characters; every other scalar field value is limited to 1,000.
      - `readonly` boolean, required: Only valid for sender-owned fields. Sender fields are always static in signer sessions.
      - `validation` object, nullable, required
        - `minLength` integer
        - `maxLength` integer
        - `pattern` string: Safe JavaScript regular expression without flags.
        - `min` number
        - `max` number
        - `minDate` string, date
        - `maxDate` string, date
      - `options` object[], nullable, required: Required for select/radio and forbidden for other types. Option values must be unique.
        - `value` string, required
        - `label` string, required
      - `pageIndex` integer, required: 0-based page, and it must be inside the document's page count.
      - `xRatio` number, required: Left edge as a fraction of page width, from the left.
      - `yRatio` number, required: Top edge as a fraction of page height, from the top.
      - `widthRatio` number, required: Width as a fraction of page width. `xRatio + widthRatio` must stay within the page.
      - `heightRatio` number, required: Height as a fraction of page height. `yRatio + heightRatio` must stay within the page.
      - `required` boolean, required: Whether the signer must fill this field before the envelope can complete.
      - `label` string, nullable, required: Shown to the signer beside the field. Most useful on `text` and `date`, where the field alone does not say what is wanted.
      - `detectionSource` 'agent' | 'manual' | 'agent-then-manual', required: Where the placement came from. Pass through what field detection returned so the provenance survives; omit it for hand-placed fields.
      - `detectionConfidence` number, nullable, required: The detector's confidence, when the placement came from detection.
      - `detectionReason` string, nullable, required: Why the detector placed the field here.
      - `id` string, required
    - `readiness` EnvelopeReadiness, required: Computed send readiness. It is never persisted and may change after a draft edit.
      - `canSend` boolean, required: True when no readiness issue blocks sending. Credit availability is separate.
      - `issues` ReadinessIssue[], required
        - union
          - MissingSenderValueIssue
            - `code` 'missing_sender_value', required
            - `fieldId` string, required
            - `name` string, nullable, required
            - `label` string, nullable, required
          - InvalidSenderValueIssue
            - `code` 'invalid_sender_value', required
            - `fieldId` string, required
            - `name` string, nullable, required
            - `detail` string, required: Safe, human-readable validation detail.
          - UnassignedFieldIssue: Reserved for forward compatibility. Current persisted envelopes do not produce this issue.
            - `code` 'unassigned_field', required
            - `fieldId` string, required
          - NoSignerFieldsIssue
            - `code` 'no_signer_fields', required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
    - `cc` CcRecipientSummary[], required: Copy recipients on this envelope.
      - `id` string, required
      - `name` string, required
      - `email` string, email, required
    - `creditCost` integer, required: Credits sending this envelope costs: one for the envelope, one for each signer who is not the sender, and one more when `emailVerification` is on. An unmetered workspace is quoted the same price and charged nothing.
    - `creditLines` CreditLine[], required: The same total broken into its lines.
      - `kind` 'envelope' | 'signer' | 'otp', required
      - `quantity` integer, required: How many units this line charges for.
      - `unitCredits` integer, required: Credits per unit.
      - `credits` integer, required: `quantity` times `unitCredits`.

## Other responses

- `400`: The request is malformed or the resource is in the wrong state for a synchronous check. Codes: `validation_error`, `invalid_body`, `invalid_parameter`, `invalid_idempotency_key`, `invalid_event_id`, `invalid_file`, `invalid_url`, `invalid_status`, `key_mismatch`, `no_signers`, `not_completed`, `envelope_exists`, `document_not_ready`, `upload_failed`.
- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `409`: document_in_use or envelope_exists: another template or envelope already owns this document.
- `422`: The draft contract failed validation, or `recipient_denied` refused a signer or CC on the workspace deny list.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

[API](https://skmtc.dev/inkseal/apis/inkseal-api.md) · [All operations](https://skmtc.dev/inkseal/apis/inkseal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inkseal/apis/inkseal-api/revisions/c440c981d578?raw)
