A document runs exactly one transaction, so a copy is what gives you a second run — of a finished agreement, or of a master document you keep and copy per counterparty. A view link then lets somebody read a document without a Circularo account. This guide does both in two calls.
Before you begin
-
A document to copy. The examples copy an employment agreement that carries a category, metadata and one attachment.
-
Copying attachments needs the
view_attachmentsright. Without it the copy is still created, just without the attachments.
Step 1: Copy the document
Cloning takes no input beyond the document you are copying:
POST /documents/pXq4NcYBhK9tWdA2mFzR/clone
Authorization: Bearer YOUR_API_KEY
HTTP/2 201
{
"id": "hL8vXe3RmQ7zBnT5wKdY",
"title": "Copy of Employment Agreement (template)",
"category": "Contract",
"metadataDefinition": "d_employment",
"metadataFields": {
"D_EMPLOYMENT_DEPARTMENT": "Engineering"
},
"attachments": [
{
"fileId": "f21b7e9d54a0c836be47d0a95c1e628f",
"fileName": "Benefits summary",
"isMerged": false,
"isPublic": false,
"mimeType": "application/pdf",
"pageCount": 3,
"size": 818513
}
],
"mainFile": {
"fileId": "8d3f60ba14c7e295db0a7f36e14b95c2",
"fileName": "Employment Agreement (template)",
"mimeType": "application/pdf",
"pageCount": 1,
"size": 1852
},
"folders": [
{
"folderId": null,
"scope": "personal"
}
],
"transaction": {
"id": "Kd3PmZq8XvR5wTnB6hLc",
"status": "not-started",
"waitingFor": []
},
"templateId": null,
...
}
A few things to notice:
-
The copy is made from the document's current state — content, metadata, field values and attachments come along, and the title gains a
Copy ofprefix. -
The copy owns its files:
mainFile.fileIdand the attachment identifiers are new, so working on one document never touches the other. -
transactionis a fresh one — a differentid, back tonot-started. Nothing tied to the original's transaction comes along: the fields placed for its recipients and the files they submitted are left behind, so the copy's own transaction places its fields anew. -
templateIdis the one binding cloning does not release. It isnullhere because no template governs either document; had the original been created from a template, the copy would stay bound to the same one.
folders shows where the copy landed: the root of your personal tree, because the request named no target. Pass folderId or scope to place it somewhere else.
Step 2: Create a view link for the copy
A view link is created by asking the document for one:
POST /documents/hL8vXe3RmQ7zBnT5wKdY/link
Authorization: Bearer YOUR_API_KEY
HTTP/2 201
{
"shareToken": "82c5fa30d26bc80b828c2c6e8ced7f2655b0f1539a25a8ac888ccc75b30363f0",
"shareUrl": "https://sandbox.circularo.com/share?shareToken=82c5fa30d26bc80b828c2c6e8ced7f2655b0f1539a25a8ac888ccc75b30363f0"
}
shareUrl is ready to hand out — it opens the document, read-only, in the Circularo web app, and the recipient needs no account and no signing invitation. shareToken is the raw token the URL carries, for the cases where your own system stores it.
Asking again returns the same link: it is a permanent property of the document, not a new grant each time.
The link never expires, anyone who has it can view the document, and the API has no way to withdraw it — a link you have handed out stays valid for as long as the document exists. Treat it as a secret, and use it only where that is acceptable.
A view link is not how you ask somebody to sign. A recipient of a transaction gets their own link, which identifies them and lets them act — see Hand out signing links yourself.
Complete example
Copying a document and publishing a view link to the copy, as a Node.js script. Error handling is minimal for brevity — in production, inspect the error envelope of non-2xx responses.
const BASE_URL = "https://sandbox.circularo.com/api/v1/public"; // your instance's base URL
const API_KEY = "YOUR_API_KEY";
const AUTH_HEADER = { "Authorization": `Bearer ${API_KEY}` };
const documentId = "pXq4NcYBhK9tWdA2mFzR"; // the document to copy
const cloneRes = await fetch(`${BASE_URL}/documents/${documentId}/clone`, { method: "POST", headers: AUTH_HEADER });
if (!cloneRes.ok) throw new Error(`Cloning failed: ${cloneRes.status}`); // error handling kept minimal for brevity
const copy = await cloneRes.json();
const linkRes = await fetch(`${BASE_URL}/documents/${copy.id}/link`, { method: "POST", headers: AUTH_HEADER });
if (!linkRes.ok) throw new Error(`Link failed: ${linkRes.status}`);
console.log(`Copy ${copy.id} can be read at ${(await linkRes.json()).shareUrl}`);
Next steps
-
Run a transaction on the copy — Signing & Approvals.
-
Retry a copy safely after a timeout, so one document is never copied twice — Retry requests safely.