Before a document goes anywhere, your integration can add its own content to it — a note, a logo — and then seal it, producing cryptographic proof that it has not changed since. This guide does both, in that order, in two calls.
Before you begin
-
A document nobody has acted on yet. Annotations are accepted only before the document has been sealed and before its transaction has started.
-
An uploaded image, if you want to place one. The examples embed a PNG uploaded as in Upload and download files.
Step 1: Add a note and an image
Text goes into annotations, images into images, and each element says where it belongs:
POST /documents/pXq4NcYBhK9tWdA2mFzR/annotations
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"annotations": [
{
"text": "Reviewed by Finance",
"pages": [
1
],
"position": {
"x": 0.08,
"y": 0.82,
"width": 0.35,
"height": 0.04
}
}
],
"images": [
{
"fileId": "e94b7c1f25a80d63b7f0c4e18a5d7b62",
"pages": [
1
],
"position": {
"x": 0.72,
"y": 0.05,
"width": 0.16,
"height": 0.08
}
}
]
}
HTTP/2 200
{
"id": "pXq4NcYBhK9tWdA2mFzR",
"title": "Employment Agreement (Jane Doe)",
"annotations": [
{
"addedAt": "2026-05-11T09:30:00.000Z",
"addedBy": "john.smith@example.com",
"comment": null,
"text": "Reviewed by Finance",
"type": "annotation_generic"
}
],
"images": [
{
"addedAt": "2026-05-11T09:30:00.000Z",
"addedBy": "john.smith@example.com",
"comment": null,
"fileId": "e94b7c1f25a80d63b7f0c4e18a5d7b62"
}
],
...
}
A few things to notice:
-
pageslists the pages an element is placed on, andpositionplaces it there. The values are fractions of the page, the same coordinate system placed signature fields use — Place fields on the document explains it in detail. -
An image is not uploaded here:
fileIdreferences a file you uploaded beforehand, and it is embedded into the document at the position you give it. -
The response is the updated document.
annotationsandimageslist everything it now carries, each with who added it and when. -
An annotation's
typesays what it represents; left out, it is a plain piece of text.
The note and the image are part of the document now, not a comment attached to it, and every later read returns them.
Step 2: Seal the document
A seal is applied to the document as a whole, so the request only says which proof to add:
POST /documents/pXq4NcYBhK9tWdA2mFzR/seal
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"useCertificate": true
}
HTTP/2 200
{
"id": "pXq4NcYBhK9tWdA2mFzR",
"title": "Employment Agreement (Jane Doe)",
"signatures": [
{
"comment": null,
"signatureProvider": "internal",
"signedAt": "2026-05-11T09:32:00.000Z",
"signedBy": "john.smith@example.com",
"type": "seal"
}
],
"mainFile": {
"fileId": "6c1e83b5f7a9042dcb8e5f217a3d90c4",
"fileName": "Employment Agreement (Jane Doe)",
"mimeType": "application/pdf",
"pageCount": 1
},
...
}
A few things to notice:
-
useCertificateseals the document with a digital certificate;useTimestampadds a trusted timestamp from a Time Stamping Authority, proving when it was sealed. Enable at least one — a request with neither is rejected with400. -
The seal joins the document's
signatures, markedtype: seal, together with any signatures the document already carries. -
Sealing writes the sealed PDF, so
mainFile.fileIdnow points at a new file. Download it as in Upload and download files. -
Sealing consumes transaction capacity from your subscription.
A seal does not close the document's process: you can still send a sealed document for signature.
Add your annotations before you seal the document or send it to anyone. Afterwards the same call is refused with 409 — and the only way to annotate that content is a fresh copy of the document.
Annotating and sending can happen in one call: POST /transactions takes apply.annotations and apply.images next to send, adding exactly the same elements as the transaction starts. Use the endpoint on this page when nothing is being sent.
Complete example
Annotating a document and sealing it, 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 HEADERS = { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" };
const documentId = "pXq4NcYBhK9tWdA2mFzR"; // the document to work on
const annotated = await fetch(`${BASE_URL}/documents/${documentId}/annotations`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
annotations: [
{
text: "Reviewed by Finance",
pages: [
1
],
position: {
x: 0.08,
y: 0.82,
width: 0.35,
height: 0.04
}
}
],
images: [
{
fileId: "e94b7c1f25a80d63b7f0c4e18a5d7b62",
pages: [
1
],
position: {
x: 0.72,
y: 0.05,
width: 0.16,
height: 0.08
}
}
]
})
});
if (!annotated.ok) throw new Error(`Annotating failed: ${annotated.status}`); // error handling kept minimal for brevity
const sealed = await fetch(`${BASE_URL}/documents/${documentId}/seal`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
useCertificate: true
})
});
if (!sealed.ok) throw new Error(`Sealing failed: ${sealed.status}`);
const document = await sealed.json();
console.log(`Sealed document ${document.id}, main file is now ${document.mainFile.fileId}`);
Next steps
-
Send the document to recipients instead of finishing it yourself — Signing & Approvals.
-
Copy a document that can no longer be edited — Copy a document and share a view link.