Topolo Docs Topolo Platform
Open workspace

Create envelope

Create a signature envelope as a DRAFT. Nothing is sent and no signer is contacted; the envelope stays editable until envelopes.send. Seed it from an active template, or supply the document, fields and recipients directly.

Outcome

Create a signature envelope as a DRAFT. Nothing is sent and no signer is contacted; the envelope stays editable until envelopes.send. Seed it from an active template, or supply the document, fields and recipients directly.

Before you start

  • Verify the environment, authenticated identity and specifically approved organization context.
  • The published application permission is workspace:write. Resolve and bind the exact canonical Sign workspace. A supplied id or organization membership alone grants no workspace access.
  • Session callers require read_write or admin workspace access. API-key resource binding is verified by Auth; never treat a caller-supplied header as an independent grant.
  • Approve the documented mutation and actual recipient/document targets before CLI --confirm or MCP confirm:true. A synthetic fixture authorizes no live agreement, notification or data deletion.

Discover the target

topolo whoami --json
topolo actions get app_topolo_sign.envelopes.create --detail --json
topolo resources topolo-sign --json
topolo workspaces list topolo-sign --json
  • Set SIGN_WORKSPACE_ID to the exact granted workspace returned by discovery.
  • Discover templateId through templates.list/get; confirm active status before creating an envelope from it.
  • Discover documentId and documentRef from upload or authorized retained metadata; they are distinct identifiers.

Request

POST /api/envelopes

Inspect and test this action. Save the reviewed input as payload.json. Supply resource context separately.

Input fields

FieldRequiredMeaningConstraints
templateIdOptionalExact template.id from templates.list/get; creating from a template requires active status.{"minLength":1}
titleOptionalEnvelope display title; creation may derive it from the template. Draft updates preserve supported existing scalar values.{"minLength":1}
subjectOptionalRecipient message subject. Sending requires nonempty subject and message.{}
messageOptionalRecipient message. Editing a draft must also supply its full intended recipient list.{}
sourceAppOptionalOptional originating application metadata; this does not grant permissions or discover a resource.{"minLength":1}
sourceRefOptionalOptional origin resource reference metadata; this is not an authorization boundary.{"minLength":1}
documentTitleOptionalHuman document title metadata.{"minLength":1}
documentRefOptionalExact immutable document.documentRef returned by documents.upload. Never invent an object key or file path.{"minLength":1}
fieldsOptionalStructured version-2 field objects with type, label, assignee and page placement. Geometry uses top-left page fractions. Send freezes the source and fields.{"maxItems":200}
fieldSchemaOptionalLegacy field-name input upgraded by the current source; use structured fields for explicit assignee, capacity and geometry.{}
recipientsOptionalComplete replacement list, at most 20. Omission during draft update clears recipients. Each has name, email, role, optional routingOrder and capacities; discover actual recipient identities and obtain authorization before notification.{"maxItems":20}

Read the complete input schema.

Effects

  • Creates an envelope in DRAFT status.
  • Sends nothing and contacts no signer — recipients are recorded, not notified.
  • Copies the document, field schema and defaults from the template when templateId is supplied.
  • Fields are structured (schema version 2): each has a type, label, required flag, an assignee {role, capacity} and page placement. The legacy fieldSchema name list is still accepted and upgraded.
  • Recipients may carry a routingOrder (sequential signing) and company capacities (one person signing personally and for a company).

Interpret the response

Read $.envelope.id for the returned envelope identifier. An empty collection does not discover a target.

Expect 201 and read $.envelope.id. Confirm $.envelope.status is "draft" — creating an envelope never sends it. Call envelopes.get and check $.envelope.fields (fieldSchemaVersion 2) and the recipients before sending.

Inspect the success schema. CLI/MCP add their transport envelope; --detail requests the full CLI result. HTTP 204 contains no JSON payload.

Worked examples

Captured from the exported Sign Worker with the existing SQLite-backed D1 harness applying all core, envelope and audit migrations to one local database, in-memory R2 and synthetic Auth/workspace/archive/Notify transports. These examples prove local validation, persistence and artifact production. They do not prove separate deployed D1 topology, real recipient consent, provider delivery or a live signed agreement. All identities, signing links and documents are synthetic; replace identifiers through authorized discovery and never execute a fixture against a live organization. Binary download examples use the released SDK binary projection; tests Base64-decode and compare original bytes, lengths and server SHA-256 headers.

Supported request · Create a draft without notifying a recipient

Input · payload.json:

{
  "templateId": "8a821f8b-b0d1-4808-a92e-8d3f16e4228f",
  "title": "Fixture agreement",
  "recipients": [
    {
      "role": "Signer",
      "name": "Fixture Recipient",
      "email": "recipient@example.test"
    }
  ]
}

CLI:

topolo actions validate app_topolo_sign.envelopes.create --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.create --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_sign.envelopes.create --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --confirm --detail --json

MCP tool and arguments:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_sign.envelopes.create",
    "input": {
      "templateId": "8a821f8b-b0d1-4808-a92e-8d3f16e4228f",
      "title": "Fixture agreement",
      "recipients": [
        {
          "role": "Signer",
          "name": "Fixture Recipient",
          "email": "recipient@example.test"
        }
      ]
    },
    "resourceType": "workspace",
    "resourceId": "<exact granted id from discovery>",
    "confirm": true,
    "detail": true
  }
}

Captured exported Worker response · HTTP 201:

{
  "envelope": {
    "id": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
    "templateId": "8a821f8b-b0d1-4808-a92e-8d3f16e4228f",
    "title": "Fixture agreement",
    "subject": "Review the fixture",
    "message": "Synthetic local example only.",
    "status": "draft",
    "sourceApp": null,
    "sourceRef": null,
    "documentTitle": "Fixture agreement",
    "documentRef": "sign-document:92c3741e-171c-439c-9419-597ffe485224",
    "fieldSchemaVersion": 2,
    "fields": [
      {
        "id": "fixture_signature",
        "type": "signature",
        "label": "Signature",
        "required": true,
        "assignee": {
          "role": "Signer",
          "capacity": "personal"
        },
        "placement": {
          "page": 1,
          "x": 0.1,
          "y": 0.7,
          "width": 0.3,
          "height": 0.08
        }
      }
    ],
    "fieldSchema": [
      "fixture_signature"
    ],
    "source": null,
    "completedSha256": null,
    "correlationId": "sgn_21282514-5ec2-4694-b413-1c113e14193b",
    "completedAt": null,
    "voidedAt": null,
    "createdByEmail": "operator@example.test",
    "createdAt": "2026-10-05T18:07:40.462Z",
    "updatedAt": "2026-10-05T18:07:40.462Z",
    "recipients": [
      {
        "id": "3d86fd6d-0500-49fd-bea4-4c7186a3cb87",
        "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
        "role": "Signer",
        "name": "Fixture Recipient",
        "email": "recipient@example.test",
        "status": "draft",
        "routingOrder": 1,
        "capacities": [
          {
            "key": "personal",
            "kind": "personal"
          }
        ],
        "viewedAt": null,
        "signedAt": null,
        "declinedAt": null,
        "typedName": null,
        "evidenceSha256": null,
        "linkIssuedAt": null,
        "createdAt": "2026-10-05T18:07:40.462Z",
        "updatedAt": "2026-10-05T18:07:40.462Z"
      }
    ],
    "auditEvents": [
      {
        "id": "bb368dd7-b119-44df-90cc-51647043472f",
        "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
        "recipientId": null,
        "eventType": "envelope.created",
        "actorType": "user",
        "actorEmail": "operator@example.test",
        "metadata": {
          "recipientCount": 1,
          "fieldCount": 1,
          "sourceApp": null,
          "sourceRef": null
        },
        "createdAt": "2026-10-05T18:07:40.462Z"
      }
    ]
  }
}

HTTP 201. Expect 201 and read $.envelope.id. Confirm $.envelope.status is "draft" — creating an envelope never sends it. Call envelopes.get and check $.envelope.fields (fieldSchemaVersion 2) and the recipients before sending.

Supported request · Create a second untouched draft

Input · payload.json:

{
  "title": "Disposable local draft"
}

CLI:

topolo actions validate app_topolo_sign.envelopes.create --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.create --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_sign.envelopes.create --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --confirm --detail --json

MCP tool and arguments:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_sign.envelopes.create",
    "input": {
      "title": "Disposable local draft"
    },
    "resourceType": "workspace",
    "resourceId": "<exact granted id from discovery>",
    "confirm": true,
    "detail": true
  }
}

Captured exported Worker response · HTTP 201:

{
  "envelope": {
    "id": "e4fc2bcd-481c-450a-bdbd-fbbbc93f6865",
    "templateId": null,
    "title": "Disposable local draft",
    "subject": "Disposable local draft",
    "message": "",
    "status": "draft",
    "sourceApp": null,
    "sourceRef": null,
    "documentTitle": "Disposable local draft",
    "documentRef": null,
    "fieldSchemaVersion": 2,
    "fields": [],
    "fieldSchema": [],
    "source": null,
    "completedSha256": null,
    "correlationId": "sgn_8f7a0d29-cd77-4158-a40a-0f52804b6a89",
    "completedAt": null,
    "voidedAt": null,
    "createdByEmail": "operator@example.test",
    "createdAt": "2026-10-05T18:07:40.588Z",
    "updatedAt": "2026-10-05T18:07:40.588Z",
    "recipients": [],
    "auditEvents": [
      {
        "id": "bf675b77-d67d-4964-a1e2-43d8e90a24fa",
        "envelopeId": "e4fc2bcd-481c-450a-bdbd-fbbbc93f6865",
        "recipientId": null,
        "eventType": "envelope.created",
        "actorType": "user",
        "actorEmail": "operator@example.test",
        "metadata": {
          "recipientCount": 0,
          "fieldCount": 0,
          "sourceApp": null,
          "sourceRef": null
        },
        "createdAt": "2026-10-05T18:07:40.588Z"
      }
    ]
  }
}

HTTP 201. Expect 201 and read $.envelope.id. Confirm $.envelope.status is "draft" — creating an envelope never sends it. Call envelopes.get and check $.envelope.fields (fieldSchemaVersion 2) and the recipients before sending.

Refused request · HTTP 401

Input · payload.json:

{
  "templateId": "aee5622d-8cf4-41bc-873a-e2b0fe0f454f",
  "title": "Fixture agreement",
  "recipients": [
    {
      "role": "Signer",
      "name": "Fixture Recipient",
      "email": "recipient@example.test"
    }
  ]
}

CLI:

topolo actions validate app_topolo_sign.envelopes.create --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.create --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json

Captured exported Worker response · HTTP 401:

{
  "error": "Token validation failed"
}

HTTP 401. Resolve the exact refusal before retrying. This request is not a successful workflow.

Refused request · HTTP 403

Input · payload.json:

{
  "templateId": "aee5622d-8cf4-41bc-873a-e2b0fe0f454f",
  "title": "Fixture agreement",
  "recipients": [
    {
      "role": "Signer",
      "name": "Fixture Recipient",
      "email": "recipient@example.test"
    }
  ]
}

CLI:

topolo actions validate app_topolo_sign.envelopes.create --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.create --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json

Captured exported Worker response · HTTP 403:

{
  "error": "This workspace requires read_write access",
  "code": "workspace_access_insufficient"
}

HTTP 403. Resolve the exact refusal before retrying. This request is not a successful workflow.

Verify the result

  • Expect 201 and read $.envelope.id.
  • Confirm $.envelope.status is "draft" — creating an envelope never sends it.
  • Call envelopes.get and check $.envelope.fields (fieldSchemaVersion 2) and the recipients before sending.

Errors and recovery

  • organization_required (HTTP 403): The token carries no organization scope. Re-authenticate against the organization that owns the workspace and retry.
  • template_not_found (HTTP 404): No template in this workspace carries that id. Call templates.list and use an id from the response.
  • template_not_active (HTTP 409): Only an active template can seed an envelope. Call templates.update with status "active" first, or supply the document, fields and recipients directly.
  • invalid_fields (HTTP 400): fields must be an array of field objects (at most 200). Each needs an id (lowercase, digits, _ or -), a type and a label; detail names the offending field index or id.
  • invalid_field_type (HTTP 400): type must be one of signature, initials, full_name, position, date, text, checkbox or company_capacity.
  • invalid_field_placement (HTTP 400): placement is {page, x, y, width, height}: page is 1-based, the rest are fractions (0-1) of the displayed page from its top-left corner. Use null for the signature page.
  • field_outside_page (HTTP 400): The box runs off the page: x + width and y + height must each be at most 1, and width and height must be positive.
  • duplicate_field_id (HTTP 400): Every field id must be unique within the envelope; detail names the repeated id.
  • invalid_routing_order (HTTP 400): routingOrder is a whole number from 1 to 20. Recipients sharing an order sign in parallel; higher orders wait for every lower one.
  • capacity_company_required (HTTP 400): A company capacity needs companyName. Add {key, kind: "company", companyName, title?} to the recipient's capacities.
  • too_many_recipients (HTTP 400): An envelope takes at most 20 recipients. Split the signing into separate envelopes.
  • auth_error (HTTP 401): Reauthenticate in the same approved context before retrying.
  • workspace_access_insufficient (HTTP 403): Discover the actual workspace grant and obtain the documented required access; do not retry with a guessed workspace or broader organization.

Some existing refusals return a human message as error rather than a stable code; preserve the actual response, status and request id. Platform validation, authentication and resource checks can refuse before the handler.

Recovery boundary

A draft has reached nobody, so it costs nothing to leave. Void it with envelopes.void if it should not be actionable at all.

  • Update envelope: Edit a draft envelope, replacing its recipients with the complete supplied list. Omitted recipients are cleared; sent or closed envelopes cannot be edited.
  • Send envelope: Freeze the source and fields, revoke previous unsigned links, issue new signing links and queue recipient notifications in routing order. Notify acceptance is distinct from actual recipient delivery.
  • Get Sign envelope: Get one signature envelope with its document, field schema, recipients and their individual signing status, and the delivery record for every party message ($.envelope.deliveries: signing requests and completed copies, with Notify dispatch outcome).
  • Void envelope: Void a signature envelope so its outstanding signing links stop working and no further signature can be collected. A completed envelope cannot be voided; voiding is the only way to retract one already sent.

Evidence

Captured from the exported Sign Worker with the existing SQLite-backed D1 harness applying all core, envelope and audit migrations to one local database, in-memory R2 and synthetic Auth/workspace/archive/Notify transports. These examples prove local validation, persistence and artifact production. They do not prove separate deployed D1 topology, real recipient consent, provider delivery or a live signed agreement. All identities, signing links and documents are synthetic; replace identifiers through authorized discovery and never execute a fixture against a live organization. Binary download examples use the released SDK binary projection; tests Base64-decode and compare original bytes, lengths and server SHA-256 headers.