Annotate and seal a document

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:

  • pages lists the pages an element is placed on, and position places 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: fileId references a file you uploaded beforehand, and it is embedded into the document at the position you give it.

  • The response is the updated document. annotations and images list everything it now carries, each with who added it and when.

  • An annotation's type says 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:

  • useCertificate seals the document with a digital certificate; useTimestamp adds a trusted timestamp from a Time Stamping Authority, proving when it was sealed. Enable at least one — a request with neither is rejected with 400.

  • The seal joins the document's signatures, marked type: seal, together with any signatures the document already carries.

  • Sealing writes the sealed PDF, so mainFile.fileId now 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.

Important

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.

Note

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.

JavaScript
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