Topolo Docs Topolo Platform
Open workspace

Update Compose document

Update one document from supplied fields. Omitted values retain existing state; the operation creates no version snapshot, and approval fields do not complete the review workflow.

Outcome

Update one document from supplied fields. Omitted values retain existing state; the operation creates no version snapshot, and approval fields do not complete the review workflow.

Before you start

  • Verify the authenticated identity, environment and pinned organization before preparing an action.
  • The published application permission is workspace:write. Discover and select a granted Compose workspace separately from action input. A supplied identifier does not grant access.
  • A read_write workspace grant is required, including model suggestion previews that do not persist document text.
  • Authorize the documented effects or private-data export before CLI --confirm or MCP confirm:true. HTTP method alone does not establish a read-only operation.

Discover the target

topolo whoami --json
topolo actions get app_topolo_compose.documents.update --detail --json
topolo resources topolo-compose --json
topolo workspaces list topolo-compose --json
  • Select the granted workspace id as COMPOSE_WORKSPACE_ID. Use document and version ids returned by accessible reads; fixture ids confer no live access.

Request

PATCH /api/documents/{documentId}

Save action input as payload.json. Inspect the plan for the exact path, query and body. Set COMPOSE_WORKSPACE_ID from granted resource discovery.

Inspect and test this action in the signed action console, then perform the verification below.

Input fields

FieldRequiredTypeConstraints
titleOptionalstring{"minLength":1}
bodyMarkdownOptionalstring{}
markdownOptionalstring{}
documentTypeOptionalstring{"enum":["contract","scientific_paper","maths_paper","letterhead","proposal","report","policy","general"]}
typeOptionalstring{"enum":["contract","scientific_paper","maths_paper","letterhead","proposal","report","policy","general"]}
stylePresetOptionalSee exact schema{}
styleOptionalstring{"enum":["executive","legal","academic","technical","brand"]}
audienceOptionalstring{}
sourceMaterialOptionalstring{}
providerOptionalstring{}
modelOptionalstring{}
generationContractOptionalSee exact schema{}
approvalStatusOptionalstring{"enum":["draft","in_review","approved","rejected"]}
approvalOwnerOptionalstring{}
approvalDueAtOptionalstring{}
approvalNotesOptionalstring{}
documentIdRequiredstring{"minLength":1}

Exact input schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1
    },
    "bodyMarkdown": {
      "type": "string"
    },
    "markdown": {
      "type": "string"
    },
    "documentType": {
      "type": "string",
      "enum": [
        "contract",
        "scientific_paper",
        "maths_paper",
        "letterhead",
        "proposal",
        "report",
        "policy",
        "general"
      ]
    },
    "type": {
      "type": "string",
      "enum": [
        "contract",
        "scientific_paper",
        "maths_paper",
        "letterhead",
        "proposal",
        "report",
        "policy",
        "general"
      ]
    },
    "stylePreset": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "executive",
            "legal",
            "academic",
            "technical",
            "brand"
          ]
        },
        {
          "type": "null"
        }
      ]
    },
    "style": {
      "type": "string",
      "enum": [
        "executive",
        "legal",
        "academic",
        "technical",
        "brand"
      ]
    },
    "audience": {
      "type": "string"
    },
    "sourceMaterial": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "model": {
      "type": "string"
    },
    "generationContract": {},
    "approvalStatus": {
      "type": "string",
      "enum": [
        "draft",
        "in_review",
        "approved",
        "rejected"
      ]
    },
    "approvalOwner": {
      "type": "string"
    },
    "approvalDueAt": {
      "type": "string"
    },
    "approvalNotes": {
      "type": "string"
    },
    "documentId": {
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "documentId"
  ],
  "additionalProperties": false
}

Effects

  • Updates one organization/workspace document without automatically snapshotting previous text.
  • bodyMarkdown takes precedence over markdown; documentType over type, and stylePreset over style.
  • Null or blank values in most optional text fields preserve existing values; generationContract:null clears that structured field. An explicit empty bodyMarkdown clears text.
  • Can set approval metadata directly, but does not record a reviewer decision or emit the completed-review workflow.

Interpret the response

Use response path $.document.id for compose_document. An empty list does not discover a target.

Expect HTTP 200 with the same document.id and intended field values. Read documents.get and compare only intended fields. Snapshot documents.versions.create before editing if the current text must remain recoverable.

The success schema below was checked against captured handler responses. Optional structured generation metadata and integration-dependent values can vary. Error examples retain their actual refusal shapes.

Success schema:

{
  "type": "object",
  "required": [
    "document"
  ],
  "properties": {
    "document": {
      "type": "object",
      "required": [
        "id",
        "title",
        "type",
        "style",
        "audience",
        "markdown",
        "sourceMaterial",
        "generationContract",
        "provider",
        "model",
        "approvalStatus",
        "approvalOwner",
        "approvalDueAt",
        "approvalNotes",
        "archivedAt",
        "createdByUserId",
        "createdByEmail",
        "createdAt",
        "updatedAt"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "type": {
          "type": "string"
        },
        "style": {
          "type": "null"
        },
        "audience": {
          "type": "string",
          "maxLength": 0
        },
        "markdown": {
          "type": "string"
        },
        "sourceMaterial": {
          "type": "null"
        },
        "generationContract": {
          "type": "null"
        },
        "provider": {
          "type": "null"
        },
        "model": {
          "type": "null"
        },
        "approvalStatus": {
          "type": "string"
        },
        "approvalOwner": {
          "anyOf": [
            {
              "type": "null"
            },
            {
              "type": "string"
            }
          ]
        },
        "approvalDueAt": {
          "type": "null"
        },
        "approvalNotes": {
          "type": "null"
        },
        "archivedAt": {
          "type": "null"
        },
        "createdByUserId": {
          "type": "string"
        },
        "createdByEmail": {
          "type": "string"
        },
        "createdAt": {
          "type": "string"
        },
        "updatedAt": {
          "type": "string"
        }
      }
    }
  }
}

Worked examples

Responses were captured from the actual Compose Worker using synthetic users, organizations, content and identifiers. Tests simulate Auth, Nexus, Notify, metering and platform workspace transport and use an in-memory D1 adapter or local SQLite. They prove local handler behavior, not live model correctness, provider cost, notification delivery, real workspace grants or cross-store atomicity. HTTP 204 responses are exactly empty strings in the SDK transport. Replace fixture identifiers through credential-scoped discovery. Refused examples demonstrate boundaries and must not be executed as successful workflows.

Successful request

Actual Compose Worker response. Auth, Nexus, notifications, metering and platform workspace transport are simulated. D1 is an in-memory adapter or local SQLite as stated by the test. This does not prove a live model completion or notification delivery. No request credentials are captured. Captured scenario: revision snapshots the result and requires an explicit version to preserve a later manual edit.

Input · payload.json:

{
  "markdown": "# Unsnapshotted manual edit",
  "documentId": "34c69a42-3e7f-4c74-9756-ee4a67da1f14"
}

CLI:

topolo actions validate app_topolo_compose.documents.update --data @payload.json --json
topolo actions plan app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --confirm --json

MCP tool and arguments:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_compose.documents.update",
    "input": {
      "markdown": "# Unsnapshotted manual edit",
      "documentId": "34c69a42-3e7f-4c74-9756-ee4a67da1f14"
    },
    "resourceType": "workspace",
    "resourceId": "<identifier returned by granted resource discovery>",
    "confirm": true
  }
}

Captured local response · HTTP 200:

{
  "document": {
    "id": "34c69a42-3e7f-4c74-9756-ee4a67da1f14",
    "title": "Brief",
    "type": "general",
    "style": null,
    "audience": "",
    "markdown": "# Unsnapshotted manual edit",
    "sourceMaterial": null,
    "generationContract": null,
    "provider": null,
    "model": null,
    "approvalStatus": "draft",
    "approvalOwner": null,
    "approvalDueAt": null,
    "approvalNotes": null,
    "archivedAt": null,
    "createdByUserId": "user_test_1",
    "createdByEmail": "writer@stg.topolo.us",
    "createdAt": "2026-10-04T02:26:27.223Z",
    "updatedAt": "2026-10-04T02:26:27.225Z"
  }
}

HTTP 200. Expect HTTP 200 with the same document.id and intended field values. Read documents.get and compare only intended fields. Snapshot documents.versions.create before editing if the current text must remain recoverable.

Another supported request

Actual Compose Worker response. Auth, Nexus, notifications, metering and platform workspace transport are simulated. D1 is an in-memory adapter or local SQLite as stated by the test. This does not prove a live model completion or notification delivery. No request credentials are captured. Captured scenario: document CRUD > PATCH updates a document.

Input · payload.json:

{
  "title": "After",
  "approvalStatus": "in_review",
  "approvalOwner": "Dana",
  "documentId": "f68c359b-1de7-4981-a62d-51379b79d8f9"
}

CLI:

topolo actions validate app_topolo_compose.documents.update --data @payload.json --json
topolo actions plan app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --confirm --json

MCP tool and arguments:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_compose.documents.update",
    "input": {
      "title": "After",
      "approvalStatus": "in_review",
      "approvalOwner": "Dana",
      "documentId": "f68c359b-1de7-4981-a62d-51379b79d8f9"
    },
    "resourceType": "workspace",
    "resourceId": "<identifier returned by granted resource discovery>",
    "confirm": true
  }
}

Captured local response · HTTP 200:

{
  "document": {
    "id": "f68c359b-1de7-4981-a62d-51379b79d8f9",
    "title": "After",
    "type": "general",
    "style": null,
    "audience": "",
    "markdown": "# Before",
    "sourceMaterial": null,
    "generationContract": null,
    "provider": null,
    "model": null,
    "approvalStatus": "in_review",
    "approvalOwner": "Dana",
    "approvalDueAt": null,
    "approvalNotes": null,
    "archivedAt": null,
    "createdByUserId": "user_test_1",
    "createdByEmail": "writer@stg.topolo.us",
    "createdAt": "2026-10-04T02:26:27.255Z",
    "updatedAt": "2026-10-04T02:26:27.255Z"
  }
}

HTTP 200. Expect HTTP 200 with the same document.id and intended field values. Read documents.get and compare only intended fields. Snapshot documents.versions.create before editing if the current text must remain recoverable.

Another supported request

Actual Compose Worker response. Auth, Nexus, notifications, metering and platform workspace transport are simulated. D1 is an in-memory adapter or local SQLite as stated by the test. This does not prove a live model completion or notification delivery. No request credentials are captured. Captured scenario: document versions > list, create, and restore versions.

Input · payload.json:

{
  "title": "V2",
  "markdown": "# V2 longer body here",
  "documentId": "97885e26-f7b9-41b9-8970-905f765115bb"
}

CLI:

topolo actions validate app_topolo_compose.documents.update --data @payload.json --json
topolo actions plan app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --confirm --json

MCP tool and arguments:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_compose.documents.update",
    "input": {
      "title": "V2",
      "markdown": "# V2 longer body here",
      "documentId": "97885e26-f7b9-41b9-8970-905f765115bb"
    },
    "resourceType": "workspace",
    "resourceId": "<identifier returned by granted resource discovery>",
    "confirm": true
  }
}

Captured local response · HTTP 200:

{
  "document": {
    "id": "97885e26-f7b9-41b9-8970-905f765115bb",
    "title": "V2",
    "type": "general",
    "style": null,
    "audience": "",
    "markdown": "# V2 longer body here",
    "sourceMaterial": null,
    "generationContract": null,
    "provider": null,
    "model": null,
    "approvalStatus": "draft",
    "approvalOwner": null,
    "approvalDueAt": null,
    "approvalNotes": null,
    "archivedAt": null,
    "createdByUserId": "user_test_1",
    "createdByEmail": "writer@stg.topolo.us",
    "createdAt": "2026-10-04T02:26:27.274Z",
    "updatedAt": "2026-10-04T02:26:27.275Z"
  }
}

HTTP 200. Expect HTTP 200 with the same document.id and intended field values. Read documents.get and compare only intended fields. Snapshot documents.versions.create before editing if the current text must remain recoverable.

Refused request · HTTP 404

Actual Compose Worker response. Auth, Nexus, notifications, metering and platform workspace transport are simulated. D1 is an in-memory adapter or local SQLite as stated by the test. This does not prove a live model completion or notification delivery. No request credentials are captured. Captured scenario: compose workspace data isolation > a cross-workspace mutation (PATCH/DELETE) fails closed with 404.

Input · payload.json:

{
  "title": "hijacked",
  "documentId": "ac3e2f43-0367-41aa-b862-584e02aa5f9f"
}

CLI:

topolo actions validate app_topolo_compose.documents.update --data @payload.json --json
topolo actions plan app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --json

Captured local response · HTTP 404:

{
  "error": "not_found"
}

HTTP 404. The handler refused this request. Resolve its exact cause before retrying; input validation can reject an invalid fixture before it reaches the handler.

Refused request · HTTP 403

Actual Compose Worker response. Auth, Nexus, notifications, metering and platform workspace transport are simulated. D1 is an in-memory adapter or local SQLite as stated by the test. This does not prove a live model completion or notification delivery. No request credentials are captured. Captured scenario: each grant level against each route > 'PATCH' '/api/documents/{documentId}' with a 'read' grant.

Input · payload.json:

{
  "documentId": "documentId_missing"
}

CLI:

topolo actions validate app_topolo_compose.documents.update --data @payload.json --json
topolo actions plan app_topolo_compose.documents.update --resource-type workspace --resource-id "$COMPOSE_WORKSPACE_ID" --data @payload.json --json

Captured local response · HTTP 403:

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

HTTP 403. The handler refused this request. Resolve its exact cause before retrying; input validation can reject an invalid fixture before it reaches the handler.

Verify the result

  • Expect HTTP 200 with the same document.id and intended field values.
  • Read documents.get and compare only intended fields. Snapshot documents.versions.create before editing if the current text must remain recoverable.

Errors and recovery

  • not_found (HTTP 404): Discover the accessible document and version in the selected workspace. A missing or inaccessible id is not permission to create a replacement.
  • This workspace requires read_write access (HTTP 403): Verify the authenticated organization, application permission and granted workspace. For reviewer_required, use the legitimately assigned reviewer; requesting review grants no access.

Auth, action-input validation and platform resource resolution can refuse a request before its handler. Retain the exact error, request id and context; requesting another context is not a legitimate way to bypass a refusal.

Recovery boundary

Restore a previously saved version through documents.versions.restore. The exact text replaced by an unsnapshotted update is not automatically preserved. Retain metadata separately because version restoration resets approval metadata.

  • Get Compose document: Read one document in the authorized organization and workspace, including current markdown, provenance, approval metadata and archival state. This read can initialize the local workspace mirror.
  • Save Compose document snapshot: Save a manual snapshot of the current accessible document’s text and provenance. This creates a new version without editing the current document.
  • Restore saved Compose document text: Replace current text and provenance with an accessible saved version, first saving pre_restore text and then a restore snapshot. Approval metadata resets to draft.

Evidence

Responses were captured from the actual Compose Worker using synthetic users, organizations, content and identifiers. Tests simulate Auth, Nexus, Notify, metering and platform workspace transport and use an in-memory D1 adapter or local SQLite. They prove local handler behavior, not live model correctness, provider cost, notification delivery, real workspace grants or cross-store atomicity. HTTP 204 responses are exactly empty strings in the SDK transport. Replace fixture identifiers through credential-scoped discovery. Capture digests and test scenarios are retained in the source fixture records.