ChaindocLabs

Last updated: April 28, 2026

Document management

Chaindoc stores your documents with version history, read/write access controls, and a full audit trail. Once every signer on a request completes, a hash of the final PAdES-signed PDF is anchored to the blockchain. that's your independent integrity proof.

This page covers the shape of a document, the upload flow, how versions work, how to grant access, and what blockchain anchoring actually does (and doesn't do). Learn more in the PAdES standard (ETSI).

Anatomy of a document

A Chaindoc document is a small set of fields plus a chain of versions. When you upload a file, Chaindoc stores the file in S3 (that's the "media" record), then you create a document that references that media. Each time you upload a replacement file, a new version is added to the document's history.

  • name: human-readable title
  • description: free-form text
  • hashtags: array of strings (e.g. ['#contract', '#q2-2026']) used for filtering and grouping
  • meta: array of { key, value } string pairs for custom metadata (e.g. [{ key: 'clientId', value: 'C-042' }])
  • status: draft or published in the public API
  • Versions: ordered list of file versions; each version is a separate media upload

`meta` is key/value strings, not typed fields

meta is stored as { key: string, value: string }[]. There's no built-in support for number, date, or dropdown fields. if you need typed data, serialise it yourself (e.g. ISO-8601 for dates) and parse on read.

Uploading files

Upload is a two-step process: push the binary through the media endpoint, then create a document that references the returned media object.

Supported file types

The media endpoint accepts any file with an application/*, image/*, video/*, or text/* MIME type. Common choices:

  • PDFs: recommended for signing. PAdES signatures are embedded directly into the PDF.
  • Office documents: Word, Excel, PowerPoint (.docx, .xlsx, .pptx and older variants)
  • Images: JPG, PNG, GIF, TIFF, WebP. Images are resized on upload but not converted to PDF.
  • Plain text: .txt, .rtf, .md
  • Archives / other: accepted by MIME type, stored as-is

Signing is PDF-only

Only PDF files can be signed. PAdES signatures live inside the PDF structure itself. If you need to send a Word/Office file for signature, convert it to PDF on your side first (Office has "Export as PDF" built in), upload the PDF, then create the signature request.

Size limit

The current upload API accepts files up to 250 MB. For signing workflows, keep PDFs as small as practical. compressed files render faster for signers and reduce retry pain on slow connections.

Upload via the API

import { Chaindoc } from '@chaindoc_io/server-sdk';
import { readFile } from 'node:fs/promises';

const chaindoc = new Chaindoc({
  secretKey: process.env.CHAINDOC_SECRET_KEY!,
});

const buffer = await readFile('nda.pdf');
const blob = new Blob([buffer], { type: 'application/pdf' });

const { media } = await chaindoc.media.upload([blob]);

const doc = await chaindoc.documents.create({
  name: 'Mutual NDA. Acme',
  description: 'Two-way NDA, 3-year term',
  media: media[0],
  status: 'published',
  hashtags: ['#nda', '#acme'],
  meta: [
    { key: 'client', value: 'Acme Inc.' },
    { key: 'effectiveDate', value: '2026-05-01' },
  ],
});

Document statuses

A document has one of three top-level statuses:

  • draft: still being prepared. Not visible to signers, cannot be attached to a signature request.
  • published: live and signable. This is the state a document needs to be in before you create a signature request against it.
  • A published document can exist without a signature request yet; create a signature request when you are ready to collect signer approvals.

Individual document versions have their own lifecycle that tracks the signing state. draft, published, archived, pending_signature, and signed. Signature requests are scoped to a specific version, so older versions keep their own signed state even after the document itself moves on.

Versions

Every document starts with a single version. When you upload a replacement file, Chaindoc creates a new version. the previous one is preserved, not overwritten. The audit trail records who uploaded each version and when.

  • Signatures are tied to the specific version they were collected on, so editing a document mid-flow is safe. the signed version stays intact.
  • Blockchain anchoring happens on the version that gets fully signed, not on each version upload.
  • You can browse and download any earlier version from the document detail page.

Organising documents

Chaindoc organises documents flat, not by folders. Filter and group with hashtags and meta:

  • Hashtags: short, human-readable labels (#contract, #q2-2026, #vendor). Use them for views most of your team cares about.
  • meta pairs: structured key/value data for integration code to key off (e.g. clientId, dealId, externalRef).
  • Search: name, description, and hashtags are searchable from the dashboard and via the API. Full-text search inside the file contents is not supported. if you need that, keep your own index.

Use `meta` for IDs, hashtags for humans

Hashtags are for browsing ("show me all #contract docs"), meta is for joining ("show me docs where meta.dealId = 1234"). The two together give you both UX-friendly filtering and programmatic lookup without overloading either.

Access control

Every document has an access policy made of three parts: an accessType, a list of per-email access entries, and a list of per-role access entries. Each entry carries a level.

  • accessType: private (only the owner), public (any authenticated workspace member can read), or restricted (only the emails/roles you list).
  • accessEmails: array of { email, level }, where level is read or write.
  • accessRoles: array of { roleId, level }, pointing at team roles.

The two levels are intentionally coarse: read lets someone view and download, write adds editing (upload new versions, change metadata). There are no finer-grained tiers. signing permission is separate and is controlled by who's on the signature request, not by document access.

Set permissions programmatically through the Server SDK:

typescript
await chaindoc.documents.updateRights(documentId, {
  accessType: 'restricted',
  accessEmails: [
    { email: 'viewer@company.com', level: 'read' },
    { email: 'editor@company.com', level: 'write' },
  ],
  accessRoles: [
    { roleId: 1, level: 'read' },
  ],
});

Blockchain anchoring

Once every signer on a signature request has signed, a background job hashes the final PAdES-signed PDF and writes the hash to SKALE Calypso. You get a transactionHash and chainId back on the document's verification record; for standalone signing completion, use the request status endpoint as the authoritative trigger.

  • Anchoring is not triggered by document publishing, version uploads, or intermediate signing events. only by full completion of a signature request.
  • The on-chain record is permanent; anyone can re-hash the file years later and verify it at chaindoc.io/pdf-verify.
  • Draft and unsigned documents have no blockchain record. they're regular database rows stored in S3 until they go through signing.

Downloading documents

Download the original file or any specific version through the dashboard or the API. Signed PDFs come back with the PAdES signature embedded. open them in Adobe Acrobat, Apple Preview, or any compliant PDF reader and you'll see the signer identity, timestamp, and a "signed and all signatures are valid" indicator.

Audit trail

Chaindoc keeps a per-document activity log: upload, publish, version added, access change, signature request sent, signer signed, request completed, request voided. The log is a database table. the tamper-evident part is the PAdES signature and the blockchain anchor on the signed version. Use the activity log as a convenience record of who did what, use the signature + blockchain pair when you need to prove document integrity.

Best practices

  • Convert Office files to PDF before uploading if you're going to send them for signature. PAdES is PDF-only.
  • Keep signing PDFs as small as practical. Use PDF compression (downsample images, subset fonts) for anything that feels heavy.
  • Pick a hashtag scheme early and stick to it. Retagging hundreds of documents later is tedious and has no bulk-tag UI shortcut.
  • Use meta for anything a script should key off (external IDs, deal references). Use hashtags for anything a human browses.
  • Review accessEmails / accessRoles periodically. nothing auto-expires.
  • Keep your own copy of the transactionHash + chainId returned for signed documents so you have a verification trail that doesn't depend on Chaindoc's dashboard.

Troubleshooting

Upload returns 413 / file too large

Your file is too large for upload. Compress it, or split it into multiple documents if that's an option. PDFs usually lose 50-80% of their weight just by re-saving with aggressive compression.

Document can't be attached to a signature request

Check the status. signature requests require a published document, and the version needs to be in a signable state. Drafts can't be signed.

Access denied on a document you should have access to

Check the document's accessType and whether your email or role is listed. Access is evaluated against the workspace member calling the API, not the raw API key. Ask the document owner or a workspace admin to add you.

What to do next

  • Signatures. how signing flows work and what PAdES gives you
  • API documentation. full document endpoint reference with request and response examples
  • Team management. roles that you can reference from accessRoles
  • Security. API key hygiene and webhook verification
  • Webhooks. be notified when a document is signed and anchored