Real agreements rarely involve one person. This guide sends a document to two recipients who act one after another — a signer first, then an approver — and watches the transaction move from one to the next. It takes one call to send and one to track.
Before you begin
-
You have run the Quickstart: send your first document for signature. This page builds directly on it — the same inline document and signature field, now with more than one recipient.
Step 1: Send to a signer, then an approver
List every recipient in send.recipients. Set sequential to true so they act in rounds rather than all at once:
POST /transactions
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"document": {
"title": "Employment Agreement",
"fileContentBase64": "JVBERi0xLjQKJeLjz9MK..."
},
"send": {
"sequential": true,
"message": "Please review and sign the renewed employment agreement.",
"recipients": [
{
"recipient": "jane.doe@example.com",
"purpose": "sign",
"order": 1,
"message": "Please sign by Friday.",
"expiresAt": "2026-05-15T16:00:00.000Z"
},
{
"recipient": "robert.brown@example.com",
"purpose": "approve",
"order": 2
}
],
"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:
-
Each recipient has a
purpose. The first is asked tosign; the second toapprove— approvers accept the document without signing it. A transaction must ask at least one recipient to act. -
orderplaces the recipients in rounds. The signer acts in the first round; the approver waits in the second. Recipients that share anorderact together in one round. -
messagesets a note for everyone, and a per-recipientmessageadds a private one.expiresAtgives the signer a personal deadline. -
signatureFieldsplaces a signing box for the signer only. The approver signs nothing, so no field is placed for them.
HTTP/2 201
{
"id": "Vt7RfKq2LmXe5ZwGyB4N",
"status": "in-progress",
"sequential": true,
"message": "Please review and sign the renewed employment agreement.",
"recipients": [
{
"id": "Gk5cPdM9XvT3qHnU7jWs",
"recipient": "jane.doe@example.com",
"purpose": "sign",
"order": 1,
"status": "pending",
"message": "Please sign by Friday.",
"expiresAt": "2026-05-15T16:00:00.000Z",
...
},
{
"id": "Lp2WdN8qYt5vHmC4bRfZ",
"recipient": "robert.brown@example.com",
"purpose": "approve",
"order": 2,
"status": "queued",
"message": null,
"expiresAt": null,
...
}
],
...
}
The response echoes the transaction id at the top — keep it, every later call in this guide uses it. The transaction is in-progress. The signer is pending — it is their turn, and Circularo has e-mailed them — while the approver is queued, waiting for the round before theirs to finish.
To let several people act together in one round, give them the same order. Set a quorum on that round to let a subset suffice — for example, any two of three approvers. Recipients sharing a round must agree on the quorum.
Step 2: Watch the transaction advance
Once the signer signs, read the transaction again:
GET /transactions/Vt7RfKq2LmXe5ZwGyB4N
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"id": "Vt7RfKq2LmXe5ZwGyB4N",
"status": "in-progress",
"recipients": [
{
"recipient": "jane.doe@example.com",
"purpose": "sign",
"order": 1,
"status": "completed",
"completedAt": "2026-05-11T09:32:00.000Z",
...
},
{
"recipient": "robert.brown@example.com",
"purpose": "approve",
"order": 2,
"status": "pending",
"completedAt": null,
...
}
],
...
}
The signer is now completed, and the approver has become pending — the second round has begun automatically. The transaction stays in-progress until every recipient whose action is required has acted.
Step 3: See the transaction complete
Once the approver acts too, read the transaction one last time:
GET /transactions/Vt7RfKq2LmXe5ZwGyB4N
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"id": "Vt7RfKq2LmXe5ZwGyB4N",
"status": "completed",
"recipients": [
{
"recipient": "jane.doe@example.com",
"purpose": "sign",
"order": 1,
"status": "completed",
"completedAt": "2026-05-11T09:32:00.000Z",
...
},
{
"recipient": "robert.brown@example.com",
"purpose": "approve",
"order": 2,
"status": "completed",
"completedAt": "2026-05-11T09:34:00.000Z",
...
}
],
...
}
With both required actions done, the transaction status is completed and both recipients are completed. This is a terminal state — the document is now fully signed and approved, and its evidence trail is complete. A recipient who refuses ends the transaction instead, as rejected; either way the terminal status is what tells you the process is over.
Complete example
Sending to a routed set of recipients as a Node.js script. Error handling is minimal for brevity — in production, inspect the error envelope of non-2xx responses.
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}` };
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: {
sequential: true,
message: "Please review and sign the renewed employment agreement.",
recipients: [
{
recipient: "jane.doe@example.com",
purpose: "sign",
order: 1,
message: "Please sign by Friday.",
expiresAt: "2026-05-15T16:00:00.000Z"
},
{
recipient: "robert.brown@example.com",
purpose: "approve",
order: 2
}
],
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 transaction = await createRes.json();
for (const recipient of transaction.recipients) {
console.log(`Round ${recipient.order}: ${recipient.recipient} (${recipient.purpose}) is ${recipient.status}`);
}
Next steps
-
Control exactly where each field lands on the page — Place fields on the document.
-
Hand out the signing link yourself instead of relying on e-mail — Hand out signing links yourself.
-
Track changes without polling by registering a webhook — React to events with webhooks.