Topolo Docs Topolo Platform
Open workspace

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.

Outcome

Edit a draft envelope, replacing its recipients with the complete supplied list. Omitted recipients are cleared; sent or closed envelopes cannot be edited.

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.update --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.
  • Resolve envelopeId through envelopes.list/get or retain the exact creation response.
  • 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

PATCH /api/envelopes/{envelopeId}

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}
envelopeIdRequiredExact envelope.id returned by creation or envelopes.list/get in this workspace.{"minLength":1}

Read the complete input schema.

Effects

  • Replaces the editable parts of a DRAFT envelope: document, subject, message, field schema and recipients.
  • Refused once the envelope has been sent.
  • Every update replaces the complete recipient list. Omitting recipients clears it; retain and deliberately resupply the intended recipients even when changing only message or metadata.

Interpret the response

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

Call envelopes.get and confirm the changed fields read as sent and the status is still "draft". Call envelopes.get and compare the complete recipient list; supplying no recipients leaves an empty list and envelopes.send returns recipients_required.

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 · Edit the draft before freezing its source

Input · payload.json:

{
  "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
  "message": "Synthetic signing journey, no live agreement."
}

CLI:

topolo actions validate app_topolo_sign.envelopes.update --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.update --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_sign.envelopes.update --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.update",
    "input": {
      "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
      "message": "Synthetic signing journey, no live agreement."
    },
    "resourceType": "workspace",
    "resourceId": "<exact granted id from discovery>",
    "confirm": true,
    "detail": true
  }
}

Captured exported Worker response · HTTP 200:

{
  "envelope": {
    "id": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
    "templateId": "8a821f8b-b0d1-4808-a92e-8d3f16e4228f",
    "title": "Fixture agreement",
    "subject": "Review the fixture",
    "message": "Synthetic signing journey, no live agreement.",
    "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.476Z",
    "recipients": [],
    "auditEvents": [
      {
        "id": "3693a899-7ef0-4e60-944b-c07d527bc692",
        "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
        "recipientId": null,
        "eventType": "envelope.updated",
        "actorType": "user",
        "actorEmail": "operator@example.test",
        "metadata": {
          "recipientCount": 0,
          "fieldCount": 1,
          "sourceApp": null,
          "sourceRef": null
        },
        "createdAt": "2026-10-05T18:07:40.476Z"
      },
      {
        "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 200. Call envelopes.get and confirm the changed fields read as sent and the status is still "draft". Call envelopes.get and compare the complete recipient list; supplying no recipients leaves an empty list and envelopes.send returns recipients_required.

Supported request · Supply the complete intended recipient list; update replaces recipients

Input · payload.json:

{
  "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
  "recipients": [
    {
      "role": "Signer",
      "name": "Fixture Recipient",
      "email": "recipient@example.test"
    }
  ]
}

CLI:

topolo actions validate app_topolo_sign.envelopes.update --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.update --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_sign.envelopes.update --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.update",
    "input": {
      "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
      "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 200:

{
  "envelope": {
    "id": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
    "templateId": "8a821f8b-b0d1-4808-a92e-8d3f16e4228f",
    "title": "Fixture agreement",
    "subject": "Review the fixture",
    "message": "Synthetic signing journey, no live agreement.",
    "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.489Z",
    "recipients": [
      {
        "id": "843e8126-a83e-471e-bc54-e57c07c0067d",
        "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.489Z",
        "updatedAt": "2026-10-05T18:07:40.489Z"
      }
    ],
    "auditEvents": [
      {
        "id": "57e62e82-a761-446c-980e-12689f481434",
        "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
        "recipientId": null,
        "eventType": "envelope.updated",
        "actorType": "user",
        "actorEmail": "operator@example.test",
        "metadata": {
          "recipientCount": 1,
          "fieldCount": 1,
          "sourceApp": null,
          "sourceRef": null
        },
        "createdAt": "2026-10-05T18:07:40.490Z"
      },
      {
        "id": "3693a899-7ef0-4e60-944b-c07d527bc692",
        "envelopeId": "b0db0353-6d5a-4a9b-b3f4-85f439ec4953",
        "recipientId": null,
        "eventType": "envelope.updated",
        "actorType": "user",
        "actorEmail": "operator@example.test",
        "metadata": {
          "recipientCount": 0,
          "fieldCount": 1,
          "sourceApp": null,
          "sourceRef": null
        },
        "createdAt": "2026-10-05T18:07:40.476Z"
      },
      {
        "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 200. Call envelopes.get and confirm the changed fields read as sent and the status is still "draft". Call envelopes.get and compare the complete recipient list; supplying no recipients leaves an empty list and envelopes.send returns recipients_required.

Refused request · HTTP 401

Input · payload.json:

{
  "envelopeId": "ed22fca5-d5f0-4392-8e71-96e247bf3875",
  "message": "Synthetic signing journey, no live agreement."
}

CLI:

topolo actions validate app_topolo_sign.envelopes.update --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.update --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:

{
  "envelopeId": "ed22fca5-d5f0-4392-8e71-96e247bf3875",
  "message": "Synthetic signing journey, no live agreement."
}

CLI:

topolo actions validate app_topolo_sign.envelopes.update --data @payload.json --json
topolo actions plan app_topolo_sign.envelopes.update --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

  • Call envelopes.get and confirm the changed fields read as sent and the status is still "draft".
  • Call envelopes.get and compare the complete recipient list; supplying no recipients leaves an empty list and envelopes.send returns recipients_required.

Errors and recovery

  • organization_required (HTTP 403): The token carries no organization scope. Re-authenticate against the organization that owns the workspace and retry.
  • envelope_not_found (HTTP 404): No envelope in this workspace carries that id. Call envelopes.list and use an id from the response.
  • 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.
  • envelope_not_editable (HTTP 409): Only a draft can be edited; this envelope has already been sent. Void it with envelopes.void and create a fresh envelope with the corrected terms — signers have already seen the current ones.
  • 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

Read envelopes.get first and keep the response: this replaces the recipient list and field schema rather than merging into them, so an omitted recipient is a removed recipient.

  • 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.

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.