Send a document from a template

A template is an agreement your colleagues have already prepared in the Circularo web app: the file, the fields, and who signs in which order. Your integration does not rebuild any of that — it names the people. This guide picks a template out of the catalogue, reads the roles it defines, and sends the document by casting a recipient into each role. It takes three calls.

Before you begin

  • Somebody has prepared a template in the web app. Templates are authored there, not through the API; this guide only uses one.

What a template brings, and what it does not

A template supplies exactly the things Circularo enforces when the document goes out:

  • the document: its file, its title and its metadata definition,

  • the placed fields, kept as they were drawn, and

  • the routing: which roles act, in what order, and for what purpose.

Everything else a colleague may have filled into the web app's send dialog — recipient verification, permissions, reminders, expiry, the accompanying message — is that dialog's own prefill and is not applied. Set what you need per recipient, exactly as you would without a template — Verify recipient identity covers identity checks, Send a document to several recipients in order the routing options.

Step 1: Find the template

type tells the two kinds apart: a signing template carries a document together with the roles and fields it is signed with, a document template only a starting document.

GET /templates?type=signing
Authorization: Bearer YOUR_API_KEY
HTTP/2 200

{
    "total": 1,
    "results": [
        {
            "id": "Tm4bQx7pLw2nRc9vHy5K",
            "name": "Employment Agreement",
            "type": "signing",
            "isLocked": true,
            ...
        }
    ],
    ...
}

Keep the id of the one you want. Every entry is a complete template — the same representation the next step reads — so a catalogue you already know your way around needs no second call.

Step 2: Read the roles it routes the document through

Reading one template on its own is the call to make when your integration knows the template it works with and stores its id:

GET /templates/Tm4bQx7pLw2nRc9vHy5K
Authorization: Bearer YOUR_API_KEY
HTTP/2 200

{
    "sequential": true,
    "roles": [
        {
            "order": 1,
            "purpose": "sign",
            "quorum": null,
            "recipients": [],
            "role": "Employee",
            "type": "single"
        },
        {
            "order": 2,
            "purpose": "sign",
            "quorum": null,
            "recipients": [],
            "role": "HR manager",
            "type": "single"
        }
    ],
    "signatureFields": [
        {
            "id": "employeeField0001",
            "role": "Employee",
            "position": {
                "height": 0.06,
                "width": 0.25,
                "x": 0.12,
                "y": 0.66
            },
            ...
        },
        {
            "id": "managerField00001",
            "role": "HR manager",
            "position": {
                "height": 0.06,
                "width": 0.25,
                "x": 0.57,
                "y": 0.66
            },
            ...
        }
    ],
    ...
}

Each entry in roles is a part to be played: its purpose says what that person does, order places them in the sequence, and type tells a single signer from a group. Every placed field names the role it belongs to, so you can see who signs where before anybody is invited.

An empty recipients on a role means it is yours to fill. When a role does list recipients, the template's author fixed who may play it, and you have to cast exactly those people.

Step 3: Cast the roles and send

Give each role a recipient. The document, the fields and the order all come from the template, so the body says little else:

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

{
    "templateId": "Tm4bQx7pLw2nRc9vHy5K",
    "send": {
        "recipients": [
            {
                "recipient": "jane.doe@example.com",
                "role": "Employee"
            },
            {
                "recipient": "robert.brown@example.com",
                "role": "HR manager"
            }
        ]
    }
}
HTTP/2 201

{
    "id": "Vt7RfKq2LmXe5ZwGyB4N",
    "status": "in-progress",
    "sequential": true,
    "document": {
        "id": "Dq4RfKp2LmXe5ZwGyB4N",
        "mainFileId": "da09a47b4f5b1xv726cfm36l8k1hkxzwmj5mszvfq6szoambdvc44v6i8hrhnu1f",
        "metadataDefinition": "d_default",
        "signatureProvider": null,
        "templateId": "Tm4bQx7pLw2nRc9vHy5K",
        "title": "Employment Agreement"
    },
    "recipients": [
        {
            "id": "Gk5cPdM9XvT3qHnU7jWs",
            "recipient": "jane.doe@example.com",
            "purpose": "sign",
            "order": 1,
            "status": "pending",
            ...
        },
        {
            "id": "Lp2WdN8qYt5vHmC4bRfZ",
            "recipient": "robert.brown@example.com",
            "purpose": "sign",
            "order": 2,
            "status": "queued",
            ...
        }
    ],
    ...
}

Every role must be cast — leave one out and the call is refused. The routing is the template's to decide, so order, purpose and quorum cannot be sent for a cast recipient; everything personal to them — a message, a deadline, reminders, verification — still can.

The transaction started in the template's own order: the employee is pending, the HR manager queued until the first round finishes. document.templateId records that this document is bound to the template, which is what makes Circularo re-check the fields against it on every later send.

Note

The document's templateId records the binding only for a locked template — the kind whose author marked it as the authority on how the document is signed. An unlocked template is applied here in exactly the same way, but the document it creates is not tied to it, so templateId stays empty.

Complete example

Sending from a template, 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 AUTH_HEADER = { "Authorization": `Bearer ${API_KEY}` };

// the people your own process knows, keyed by the role they play
const casting = { "Employee": "jane.doe@example.com", "HR manager": "robert.brown@example.com" };

const catalogue = await (await fetch(`${BASE_URL}/templates?type=signing`, { headers: AUTH_HEADER })).json();
const template = catalogue.results.find((item) => item.name === "Employment Agreement");
if (!template) throw new Error("Employment Agreement template not found"); // error handling kept minimal for brevity

const createRes = await fetch(`${BASE_URL}/transactions`, {
    method: "POST",
    headers: { ...AUTH_HEADER, "Content-Type": "application/json" },
    body: JSON.stringify({
        templateId: template.id,
        send: {
            recipients: template.roles.map((role) => ({ recipient: casting[role.role], role: role.role }))
        }
    })
});

const transaction = await createRes.json();
for (const recipient of transaction.recipients) {
    console.log(`Round ${recipient.order}: ${recipient.recipient} is ${recipient.status}`);
}

Next steps