Topolo Docs Topolo Platform
Open workspace

Upload signing document

Upload a PDF, PNG or JPEG as an immutable signing source. The bytes are validated, stored under their SHA-256 and never overwritten; images get a derived signing PDF, and a replacement becomes a new version.

Outcome

Upload a PDF, PNG or JPEG as an immutable signing source. The bytes are validated, stored under their SHA-256 and never overwritten; images get a derived signing PDF, and a replacement becomes a new version.

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.documents.upload --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.

Request

POST /api/documents

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

Input fields

FieldRequiredMeaningConstraints
fileNameRequiredOriginal source filename; use a PDF, PNG or JPEG with matching contentType.{"minLength":1}
contentTypeRequiredapplication/pdf, image/png or image/jpeg; source bytes are validated and hashed, not trusted from this label.{"enum":["application/pdf","image/png","image/jpeg"]}
base64RequiredBase64-encoded source bytes. Fixture bytes are synthetic and must be replaced with the specifically authorized signing document.{"minLength":1,"maxLength":34952536}
documentTitleOptionalHuman document title metadata.{"minLength":1}
previousVersionIdOptionalExact retained document version being replaced. The original object remains immutable.{"minLength":1}

Read the complete input schema.

Effects

  • Validates the file by its bytes and stores the original immutably under its SHA-256; nothing is ever overwritten.
  • Accepts PDF, PNG and JPEG. An image gets a derived one-page signing PDF; the original bytes are kept untouched.
  • Refuses encrypted PDFs, active content (JavaScript, launch/submit actions, XFA, embedded files), unreadable files and pages outside 1-200 inches.
  • With previousVersionId, creates the next version of that document and records what it supersedes; the earlier version is unchanged.
  • Records provenance: who uploaded it, the declared and detected type, the ingestor version and the request id.

Interpret the response

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

Expect 201 and read $.document.documentRef, $.document.sha256Hex and $.document.pageCount. Call documents.get and confirm ingestionStatus is "ready" and pages lists every page size.

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 · Upload a genuine one-page PDF

Input · payload.json:

{
  "fileName": "Fixture agreement.pdf",
  "contentType": "application/pdf",
  "base64": "JVBERi0xLjcKJYGBgYEKCjYgMCBvYmoKPDwKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbmd0aCAxMDYKPj4Kc3RyZWFtCnicHYqxCsJAEAX79xVbC+LuebfvDsRCECxshP0BkSQYYqFIvj9GBoYp5o1TQGXlM2B36aa5+z4f9y211VyVtYlliR7p5yvsv5owCVUlXjhk98bKzMLkJSnVzbnW3o4SI2KDc+CGBUGCF4sKZW5kc3RyZWFtCmVuZG9iagoKNyAwIG9iago8PAovRmlsdGVyIC9GbGF0ZURlY29kZQovVHlwZSAvT2JqU3RtCi9OIDUKL0ZpcnN0IDI2Ci9MZW5ndGggMzYxCj4+CnN0cmVhbQp4nNVS30vDMBB+z19xj/ogSdM2aWUMtrVVkKFsgqL40LVhVEYibSbzv/eu7SZ7EJ+lfCR3992v9AtAgIQoghB0AhHEoYQYVBDBZML449eHAf5Qbk3H+F1Td/CKUQEreGN84fbWQ8CmU/bDXZS+3LktG5IgIPKR8dC6el+ZFiZFXhRCaCGEihBKCJnhuUCkCIk2xmSCd4SORqBPh0KEM4wVA5Qecijec+MxP8cTuYo42cCNksE+9aVe+VBD/jVPOmV86eqs9AYusmsppAqEiIOEWr9c4nO0pvTu/y7Xz984++uGZ/+5cNYzvt5vfG+SM2B8XnaGIsBvze7T+KYqGc9t5erGboE/NXZmu+boOK9IgiHZtIZU1euGr0zn9m2FQiJeX5kup+JXWqQJbq6TFLU7So0/32/eTdVTycwP/mbtaavBQb6lqZty7g6oZ4GfCiToVJKqZ9Y6TzrvFW49TkOWGlWPyd+DGcXqCmVuZHN0cmVhbQplbmRvYmoKCjggMCBvYmoKPDwKL1NpemUgOQovUm9vdCAyIDAgUgovSW5mbyAzIDAgUgovRmlsdGVyIC9GbGF0ZURlY29kZQovVHlwZSAvWFJlZgovTGVuZ3RoIDQwCi9XIFsgMSAyIDIgXQovSW5kZXggWyAwIDkgXQo+PgpzdHJlYW0KeJwVxLERACAMA7G3wx0ts7MSgyVYhYBusyEpOVVa4oB4P18YYXADoAplbmRzdHJlYW0KZW5kb2JqCgpzdGFydHhyZWYKNjU4CiUlRU9G"
}

CLI:

topolo actions validate app_topolo_sign.documents.upload --data @payload.json --json
topolo actions plan app_topolo_sign.documents.upload --resource-type workspace --resource-id "$SIGN_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_sign.documents.upload --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.documents.upload",
    "input": {
      "fileName": "Fixture agreement.pdf",
      "contentType": "application/pdf",
      "base64": "JVBERi0xLjcKJYGBgYEKCjYgMCBvYmoKPDwKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbmd0aCAxMDYKPj4Kc3RyZWFtCnicHYqxCsJAEAX79xVbC+LuebfvDsRCECxshP0BkSQYYqFIvj9GBoYp5o1TQGXlM2B36aa5+z4f9y211VyVtYlliR7p5yvsv5owCVUlXjhk98bKzMLkJSnVzbnW3o4SI2KDc+CGBUGCF4sKZW5kc3RyZWFtCmVuZG9iagoKNyAwIG9iago8PAovRmlsdGVyIC9GbGF0ZURlY29kZQovVHlwZSAvT2JqU3RtCi9OIDUKL0ZpcnN0IDI2Ci9MZW5ndGggMzYxCj4+CnN0cmVhbQp4nNVS30vDMBB+z19xj/ogSdM2aWUMtrVVkKFsgqL40LVhVEYibSbzv/eu7SZ7EJ+lfCR3992v9AtAgIQoghB0AhHEoYQYVBDBZML449eHAf5Qbk3H+F1Td/CKUQEreGN84fbWQ8CmU/bDXZS+3LktG5IgIPKR8dC6el+ZFiZFXhRCaCGEihBKCJnhuUCkCIk2xmSCd4SORqBPh0KEM4wVA5Qecijec+MxP8cTuYo42cCNksE+9aVe+VBD/jVPOmV86eqs9AYusmsppAqEiIOEWr9c4nO0pvTu/y7Xz984++uGZ/+5cNYzvt5vfG+SM2B8XnaGIsBvze7T+KYqGc9t5erGboE/NXZmu+boOK9IgiHZtIZU1euGr0zn9m2FQiJeX5kup+JXWqQJbq6TFLU7So0/32/eTdVTycwP/mbtaavBQb6lqZty7g6oZ4GfCiToVJKqZ9Y6TzrvFW49TkOWGlWPyd+DGcXqCmVuZHN0cmVhbQplbmRvYmoKCjggMCBvYmoKPDwKL1NpemUgOQovUm9vdCAyIDAgUgovSW5mbyAzIDAgUgovRmlsdGVyIC9GbGF0ZURlY29kZQovVHlwZSAvWFJlZgovTGVuZ3RoIDQwCi9XIFsgMSAyIDIgXQovSW5kZXggWyAwIDkgXQo+PgpzdHJlYW0KeJwVxLERACAMA7G3wx0ts7MSgyVYhYBusyEpOVVa4oB4P18YYXADoAplbmRzdHJlYW0KZW5kb2JqCgpzdGFydHhyZWYKNjU4CiUlRU9G"
    },
    "resourceType": "workspace",
    "resourceId": "<exact granted id from discovery>",
    "confirm": true,
    "detail": true
  }
}

Captured exported Worker response · HTTP 201:

{
  "document": {
    "id": "92c3741e-171c-439c-9419-597ffe485224",
    "documentRef": "sign-document:92c3741e-171c-439c-9419-597ffe485224",
    "documentTitle": "Fixture agreement",
    "fileName": "Fixture-agreement.pdf",
    "contentType": "application/pdf",
    "sizeBytes": 861,
    "sha256Hex": "bc75a1b1ad4d41a275e3a4dadd9a6031e0c56029e486e47a181ba84891acc97f",
    "version": 1,
    "previousVersionId": null,
    "ingestionStatus": "ready",
    "pdfSha256": "bc75a1b1ad4d41a275e3a4dadd9a6031e0c56029e486e47a181ba84891acc97f",
    "pageCount": 1,
    "pages": [
      {
        "width": 612,
        "height": 792
      }
    ],
    "provenance": {
      "ingestor": "topolo-sign-ingest/2",
      "channel": "inline-base64",
      "uploadedByUserId": "usr_sign_fixture",
      "uploadedByEmail": "operator@example.test",
      "clientFileName": "Fixture agreement.pdf",
      "declaredContentType": "application/pdf",
      "detectedContentType": "application/pdf",
      "normalizedToPdf": false,
      "pdfVersion": "1.7",
      "previousVersionId": null,
      "previousSha256": null,
      "requestId": "bb3be417-ae54-41fe-82d6-bef90d5164d6",
      "ingestedAt": "2026-10-05T18:07:40.431Z"
    },
    "contentUrl": "/api/documents/92c3741e-171c-439c-9419-597ffe485224/content",
    "pdfUrl": "/api/documents/92c3741e-171c-439c-9419-597ffe485224/pdf",
    "createdByEmail": "operator@example.test",
    "createdAt": "2026-10-05T18:07:40.431Z",
    "updatedAt": "2026-10-05T18:07:40.431Z"
  }
}

HTTP 201. Expect 201 and read $.document.documentRef, $.document.sha256Hex and $.document.pageCount. Call documents.get and confirm ingestionStatus is "ready" and pages lists every page size.

Refused request · HTTP 401

Input · payload.json:

{
  "fileName": "Fixture agreement.pdf",
  "contentType": "application/pdf",
  "base64": "JVBERi0xLjcKJYGBgYEKCjYgMCBvYmoKPDwKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbmd0aCAxMDYKPj4Kc3RyZWFtCnicHYqxCsJAEAX79xVbC+LuebfvDsRCECxshP0BkSQYYqFIvj9GBoYp5o1TQGXlM2B36aa5+z4f9y211VyVtYlliR7p5yvsv5owCVUlXjhk98bKzMLkJSnVzbnW3o4SI2KDc+CGBUGCF4sKZW5kc3RyZWFtCmVuZG9iagoKNyAwIG9iago8PAovRmlsdGVyIC9GbGF0ZURlY29kZQovVHlwZSAvT2JqU3RtCi9OIDUKL0ZpcnN0IDI2Ci9MZW5ndGggMzYzCj4+CnN0cmVhbQp4nNWS30vDMBDH3/NX3KM+SC5Zf8oYbGurIEPZBEXxoWvDqIxE2kzmf+9du032ID5LOZK7+94ll34UIGgIAhhBnEAA4UhDCJEKYDwW8vHrw4B8KDemE/KuqTt4pSzCEt6EnLud9aDEZCJ+tPPSl1u3EUMRKBYfFQ+tq3eVaWFc5EWBGCNiFJBFiDqjdU6WkmnyKacT2pPFwcEoFo8QR1PKFYNF8VDD+V4bHupzWkkbsSYbtEEy+Kdz+ax86KH/uk86EXLh6qz0Bi6ya406UoihSjBQ6cslPUdrSu/+73D9/Rtnf53w7D8XznohV7u1710OKiFnZWc4A/LWbD+Nb6pSyNxWrm7sBuRTY6e2a46B844MDGPTGqaq50YuTed2bUUgsa7vzJtT86sY04Qmj5OU2D2gJp/v1++m6qXs5nt/s/I81RDg2MLUTTlze+IZ6YuUhjjVTPXUWueZ855w6+k27EUH6qn4G4v1xfAKZW5kc3RyZWFtCmVuZG9iagoKOCAwIG9iago8PAovU2l6ZSA5Ci9Sb290IDIgMCBSCi9JbmZvIDMgMCBSCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9UeXBlIC9YUmVmCi9MZW5ndGggNDAKL1cgWyAxIDIgMiBdCi9JbmRleCBbIDAgOSBdCj4+CnN0cmVhbQp4nBXEsREAIAwDsbfDHS3DsxGDJViFgG6zISk5VVrigHg/XxhhdgOiCmVuZHN0cmVhbQplbmRvYmoKCnN0YXJ0eHJlZgo2NjAKJSVFT0Y="
}

CLI:

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

{
  "fileName": "Fixture agreement.pdf",
  "contentType": "application/pdf",
  "base64": "JVBERi0xLjcKJYGBgYEKCjYgMCBvYmoKPDwKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbmd0aCAxMDYKPj4Kc3RyZWFtCnicHYqxCsJAEAX79xVbC+LuebfvDsRCECxshP0BkSQYYqFIvj9GBoYp5o1TQGXlM2B36aa5+z4f9y211VyVtYlliR7p5yvsv5owCVUlXjhk98bKzMLkJSnVzbnW3o4SI2KDc+CGBUGCF4sKZW5kc3RyZWFtCmVuZG9iagoKNyAwIG9iago8PAovRmlsdGVyIC9GbGF0ZURlY29kZQovVHlwZSAvT2JqU3RtCi9OIDUKL0ZpcnN0IDI2Ci9MZW5ndGggMzYzCj4+CnN0cmVhbQp4nNWS30vDMBDH3/NX3KM+SC5Zf8oYbGurIEPZBEXxoWvDqIxE2kzmf+9du032ID5LOZK7+94ll34UIGgIAhhBnEAA4UhDCJEKYDwW8vHrw4B8KDemE/KuqTt4pSzCEt6EnLud9aDEZCJ+tPPSl1u3EUMRKBYfFQ+tq3eVaWFc5EWBGCNiFJBFiDqjdU6WkmnyKacT2pPFwcEoFo8QR1PKFYNF8VDD+V4bHupzWkkbsSYbtEEy+Kdz+ax86KH/uk86EXLh6qz0Bi6ya406UoihSjBQ6cslPUdrSu/+73D9/Rtnf53w7D8XznohV7u1710OKiFnZWc4A/LWbD+Nb6pSyNxWrm7sBuRTY6e2a46B844MDGPTGqaq50YuTed2bUUgsa7vzJtT86sY04Qmj5OU2D2gJp/v1++m6qXs5nt/s/I81RDg2MLUTTlze+IZ6YuUhjjVTPXUWueZ855w6+k27EUH6qn4G4v1xfAKZW5kc3RyZWFtCmVuZG9iagoKOCAwIG9iago8PAovU2l6ZSA5Ci9Sb290IDIgMCBSCi9JbmZvIDMgMCBSCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9UeXBlIC9YUmVmCi9MZW5ndGggNDAKL1cgWyAxIDIgMiBdCi9JbmRleCBbIDAgOSBdCj4+CnN0cmVhbQp4nBXEsREAIAwDsbfDHS3DsxGDJViFgG6zISk5VVrigHg/XxhhdgOiCmVuZHN0cmVhbQplbmRvYmoKCnN0YXJ0eHJlZgo2NjAKJSVFT0Y="
}

CLI:

topolo actions validate app_topolo_sign.documents.upload --data @payload.json --json
topolo actions plan app_topolo_sign.documents.upload --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 $.document.documentRef, $.document.sha256Hex and $.document.pageCount.
  • Call documents.get and confirm ingestionStatus is "ready" and pages lists every page size.

Errors and recovery

  • file_required (HTTP 400): Send fileName, contentType and base64 (or a multipart form with a file field).
  • invalid_base64 (HTTP 400): base64 must be canonical base64 of the file bytes, with padding.
  • file_too_large (HTTP 413): Documents are limited to 25 MB. Compress or split the document.
  • docx_conversion_unavailable (HTTP 415): Word documents are not converted yet. Export the document to PDF and upload the PDF.
  • content_type_mismatch (HTTP 415): The declared contentType contradicts the file's bytes. Declare the real type.
  • unsupported_file_type (HTTP 415): Only PDF, PNG and JPEG can be signed.
  • pdf_encrypted (HTTP 422): Remove the password or permissions protection and upload again.
  • pdf_active_content (HTTP 422): Export a plain PDF (print to PDF) without scripts, forms actions or attachments, and upload that.
  • previous_version_not_found (HTTP 404): previousVersionId names no document in this workspace. Call documents.get with the id you meant.
  • 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

An uploaded document is inert until an envelope references it. Upload a corrected file as a new version with previousVersionId; stored versions are never deleted outside organization erasure.

  • 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.
  • 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.
  • Get Sign document metadata: Get metadata for an uploaded signing document: title, content type, size, original and signing-PDF SHA-256, version and predecessor, page count and page sizes, and upload provenance. The bytes are downloaded separately.

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.