Quickstart: send your first document for signature

In this guide you will send a PDF to a recipient for signature, track the transaction until everyone has signed, and download the signed result. Three endpoints do the whole job, and you call the tracking one again until the transaction is done.

Before you begin

You will need:

  • An API key. Create one in the Circularo application under My Account → Manage API keys, or ask your administrator for one. Every request below authenticates with the Authorization: Bearer YOUR_API_KEY header.

  • A PDF file to send for signature. Any PDF works; this guide uses employment-agreement.pdf.

All paths in this guide are relative to your instance's base URL — the examples use:

https://sandbox.circularo.com/api/v1/public
Note

Every Circularo environment — production, or a testing instance if you have one — has its own base URL and its own API keys. Point the examples at the environment your key belongs to.

Step 1: Create the transaction

A transaction is the signing or approval process of one document. A single call creates the document from your PDF and sends it to the recipient, with a signature field placed where they should sign:

POST /transactions
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
    "document": {
        "title": "Employment Agreement",
        "fileContentBase64": "JVBERi0xLjQKJeLjz9MK..."
    },
    "send": {
        "recipients": [
            {
                "recipient": "jane.doe@example.com",
                "purpose": "sign"
            }
        ],
        "signatureFields": [
            {
                "recipients": [
                    "jane.doe@example.com"
                ],
                "type": "signature",
                "pages": [
                    1
                ],
                "position": {
                    "x": 0.6,
                    "y": 0.75,
                    "width": 0.25,
                    "height": 0.08
                }
            }
        ]
    }
}

A few things to notice:

  • document creates the document inline — a title and the file content are all it needs. The fileContentBase64 value is your PDF encoded as base64 (shortened here); for large or reused files you can upload first and pass a fileId instead — see Upload and download files.

  • send.recipients lists who has to act. purpose is sign, so the recipient has to sign the document — other purposes let a recipient approve, review, accept, or just view it. A plain e-mail address is enough; the recipient does not need a Circularo account, as long as your organization allows sending outside it.

  • send.signatureFields places a field on the page for a recipient to fill. type is signature, so this is where they sign. The position values are fractions of the page size (x: 0.6 means 60 % from the left edge), so they work for any page format.

HTTP/2 201

{
    "id": "Vt7RfKq2LmXe5ZwGyB4N",
    "status": "in-progress",
    "document": {
        "id": "pXq4NcYBhK9tWdA2mFzR",
        "mainFileId": "2f7d6c0a9b1e483fa5d3c8b41e6f90aa",
        "metadataDefinition": "d_default",
        "signatureProvider": null,
        "templateId": null,
        "title": "Employment Agreement"
    },
    "recipients": [
        {
            "completedAt": null,
            "expiresAt": null,
            "id": "Gk5cPdM9XvT3qHnU7jWs",
            "isMandatory": true,
            "language": null,
            "message": null,
            "order": null,
            "permissions": [
                "print",
                "viewMetadata",
                "annotateOnReject",
                "rejectWithoutComment",
                "uploadFiles"
            ],
            "purpose": "sign",
            "quorum": null,
            "recipient": "jane.doe@example.com",
            "reminder": null,
            "status": "pending",
            "turnStartedAt": "2026-05-11T09:30:00.000Z",
            "verification": null
        }
    ],
    "webUrl": "https://sandbox.circularo.com/home/detail?select=pXq4NcYBhK9tWdA2mFzR",
    ...
}

The transaction is now in-progress and the recipient's participation is pending — Circularo has e-mailed them an invitation to open and sign the document. Keep the transaction id for tracking, and note webUrl: a ready-made link to the document detail in the Circularo application you can show to your own users.

Step 2: Track the progress

Read the transaction at any time to see where it stands:

GET /transactions/Vt7RfKq2LmXe5ZwGyB4N
Authorization: Bearer YOUR_API_KEY
HTTP/2 200

{
    "id": "Vt7RfKq2LmXe5ZwGyB4N",
    "status": "in-progress",
    "completedAt": null,
    "recipients": [
        {
            "completedAt": null,
            "expiresAt": null,
            "id": "Gk5cPdM9XvT3qHnU7jWs",
            "isMandatory": true,
            "language": null,
            "message": null,
            "order": null,
            "permissions": [
                "print",
                "viewMetadata",
                "annotateOnReject",
                "rejectWithoutComment",
                "uploadFiles"
            ],
            "purpose": "sign",
            "quorum": null,
            "recipient": "jane.doe@example.com",
            "reminder": null,
            "status": "pending",
            "turnStartedAt": "2026-05-11T09:30:00.000Z",
            "verification": null
        }
    ],
    ...
}

The transaction status stays in-progress until everyone acts, and each recipient carries their own status alongside. Polling this endpoint is fine to start with — for production integrations, register a webhook and be notified about changes instead, as React to events with webhooks shows.

completed is not the only ending. A recipient who refuses ends the transaction as rejected for everybody, a deadline that passes as expired, and calling it off yourself leaves it cancelled. All four are final, so wait for status to stop being in-progress rather than for the one you hoped for.

Step 3: Collect the result

Once the recipient signs, the same read shows the finished transaction:

GET /transactions/Vt7RfKq2LmXe5ZwGyB4N
Authorization: Bearer YOUR_API_KEY
HTTP/2 200

{
    "id": "Vt7RfKq2LmXe5ZwGyB4N",
    "status": "completed",
    "completedAt": "2026-05-11T09:34:00.000Z",
    "document": {
        "id": "pXq4NcYBhK9tWdA2mFzR",
        "mainFileId": "6c1e83b5f7a9042dcb8e5f217a3d90c4",
        "metadataDefinition": "d_default",
        "signatureProvider": "internal",
        "templateId": null,
        "title": "Employment Agreement"
    },
    "recipients": [
        {
            "completedAt": "2026-05-11T09:32:00.000Z",
            "expiresAt": null,
            "id": "Gk5cPdM9XvT3qHnU7jWs",
            "isMandatory": true,
            "language": null,
            "message": null,
            "order": null,
            "permissions": [
                "print",
                "viewMetadata",
                "annotateOnReject",
                "rejectWithoutComment",
                "uploadFiles"
            ],
            "purpose": "sign",
            "quorum": null,
            "recipient": "jane.doe@example.com",
            "reminder": null,
            "status": "completed",
            "turnStartedAt": "2026-05-11T09:30:00.000Z",
            "verification": null
        }
    ],
    ...
}

Note document.mainFileId: the signed PDF — with the signature and Circularo's evidence embedded — is a new file (signing never rewrites the content you sent).

Step 4: Download the signed PDF

The file's content endpoint returns the binary:

GET /files/6c1e83b5f7a9042dcb8e5f217a3d90c4/content
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
Content-Type: application/pdf

Store the PDF wherever your process needs it — it is the legally meaningful artifact of the whole flow.

Complete example

The whole flow as a Node.js script. Error handling is minimal for brevity — in production, inspect the error envelope of non-2xx responses.

JavaScript
import fs from "node:fs/promises";

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}` };

// Step 1: create the transaction and send it for signature
const pdfBase64 = (await fs.readFile("employment-agreement.pdf")).toString("base64");

const createRes = await fetch(`${BASE_URL}/transactions`, {
    method: "POST",
    headers: { ...AUTH_HEADER, "Content-Type": "application/json" },
    body: JSON.stringify({
        document: {
            title: "Employment Agreement",
            fileContentBase64: pdfBase64
        },
        send: {
            recipients: [
                {
                    recipient: "jane.doe@example.com",
                    purpose: "sign"
                }
            ],
            signatureFields: [
                {
                    recipients: [
                        "jane.doe@example.com"
                    ],
                    type: "signature",
                    pages: [
                        1
                    ],
                    position: {
                        x: 0.6,
                        y: 0.75,
                        width: 0.25,
                        height: 0.08
                    }
                }
            ]
        }
    })
});
if (!createRes.ok) throw new Error(`Transaction failed: ${createRes.status}`); // error handling kept minimal for brevity
const transactionId = (await createRes.json()).id;

// Step 2: poll until the transaction reaches a final state
let transaction;
do {
    await new Promise((resolve) => setTimeout(resolve, 30_000));
    transaction = await (await fetch(`${BASE_URL}/transactions/${transactionId}`, { headers: AUTH_HEADER })).json();
} while (transaction.status === "in-progress");

console.log(`Transaction finished: ${transaction.status}`);

// Step 3: download the signed PDF
if (transaction.status === "completed") {
    const content = await fetch(`${BASE_URL}/files/${transaction.document.mainFileId}/content`, { headers: AUTH_HEADER });
    await fs.writeFile("employment-agreement-signed.pdf", Buffer.from(await content.arrayBuffer()));
}

Next steps

  • Handle errors, retries, and pagination like a production integration — Robust Integrations.

  • Hand out signing links yourself, add more recipients, form fields, or identity verification — Signing & Approvals covers every variant of sending.

  • Upload and reuse files, search documents, and collect evidence — Documents & Files.