A report covers the whole organization and returns one row per document, carrying the document, its transaction, every recipient and the counts that go with them. This guide runs three of them — the whole picture, one transaction in detail, and the transactions that are still waiting.
Before you begin
-
A key that belongs to an account inside an organization. A report is always organization-wide, so an account that sits in no organization is refused with
403whatever else it may be allowed to do. The rows below are a colleague's agreements, read with an administrator's key.
Step 1: Run the report
Ask for the documents you care about, and the report comes back as an ordinary paged collection:
GET /reports?category=Contract
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"total": 3,
"results": [
{
"id": "Hn6vQ2tYbK4mXe8wRzD5",
"title": "Employment Agreement (Jane Doe)",
"status": "in-progress",
"recipientCount": 2,
"completedRecipientCount": 0,
"pendingRecipientCount": 1,
...
},
{
"id": "Pk3wRt9nBv2mYq7xLd4C",
"title": "Mutual NDA (Acme Corporation)",
"status": "completed",
"recipientCount": 1,
"completedRecipientCount": 1,
"pendingRecipientCount": 0,
...
},
{
"id": "Ws5bJm8yTn1kXe6qVz3H",
"title": "Supplier Agreement (draft)",
"status": "not-started",
"recipientCount": 0,
"completedRecipientCount": 0,
"pendingRecipientCount": 0,
...
}
],
...
}
Filters keep a report to its subject — a period through createdAfter and createdBefore, a category, a metadata definition, a creator, an owner, a transaction status. Every filter you add has to match.
A report reports on documents, so a document that has never been sent anywhere is a row like any other: status: not-started, no recipients, and counts of zero.
Paging works as everywhere else, offset for a page or two and the cursor for a full walk, described in Page through collections.
Step 2: Read one transaction in detail
Every row already holds the full detail; the overview above only left most of it out. Filtering down to a single row shows what a row really carries:
GET /reports?category=Contract&transactionStatus=completed
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"total": 1,
"results": [
{
"id": "Pk3wRt9nBv2mYq7xLd4C",
"title": "Mutual NDA (Acme Corporation)",
"category": "Contract",
"status": "completed",
"owner": "emma.wilson@example.com",
"createdBy": "emma.wilson@example.com",
"startedAt": "2026-05-11T09:38:00.000Z",
"completedAt": "2026-05-11T09:38:00.000Z",
"durationMs": 0,
"metadataFields": {
"D_DEFAULT_DESCRIPTION": "Two-year fixed term, Berlin office"
},
"completedRecipientCount": 1,
"recipients": [
{
"actedAt": "2026-05-11T09:38:00.000Z",
"actionDurationMs": 0,
"expiresAt": null,
"hasOpened": true,
"isDelegated": false,
"isSelfSigned": true,
"lastRemindedAt": null,
"order": null,
"purpose": "sign",
"receivedAt": "2026-05-11T09:38:00.000Z",
"recipient": "emma.wilson@example.com",
"recipientId": null,
"status": "completed",
"verificationMethod": null
}
],
...
}
],
...
}
Three groups of fields sit side by side. The document's own — title, owner, creator, category, and the values of its metadata fields, so a report can be grouped by your own structured data. The transaction's — when it started, when it reached its final state, and durationMs between the two, which is zero here because the owner signed their own agreement the moment it was ready. And the recipients, one entry per participation, with what each was asked to do and what they did.
isSelfSigned explains the single participation: this agreement was never sent to anybody, the owner signed it themselves, and that still counts as a completed transaction with one completed recipient.
Step 3: Find what is still outstanding
Filtering by transaction status narrows the report to the transactions that are still running:
GET /reports?category=Contract&transactionStatus=in-progress
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"total": 1,
"results": [
{
"id": "Hn6vQ2tYbK4mXe8wRzD5",
"pendingRecipientCount": 1,
"recipients": [
{
"actedAt": null,
"actionDurationMs": null,
"expiresAt": "2026-05-25T16:00:00.000Z",
"hasOpened": false,
"isDelegated": false,
"isSelfSigned": false,
"lastRemindedAt": null,
"order": 1,
"purpose": "sign",
"receivedAt": "2026-05-11T09:32:00.000Z",
"recipient": "jane.doe@example.com",
"recipientId": "f3a91c2e77d54b1a",
"status": "pending",
"verificationMethod": null
},
{
"actedAt": null,
"actionDurationMs": null,
"expiresAt": null,
"hasOpened": false,
"isDelegated": false,
"isSelfSigned": false,
"lastRemindedAt": null,
"order": 2,
"purpose": "approve",
"receivedAt": null,
"recipient": "john.smith@example.com",
"recipientId": "8c04e7b5219da6f3",
"status": "queued",
"verificationMethod": null
}
],
...
}
],
...
}
pendingRecipientCount counts the people the transaction is waiting for right now, and the recipients say who they are. In a sequential transaction only the current turn is pending; whoever comes later is queued and has not been told anything yet, which is why their receivedAt is still null. expiresAt is the deadline the first recipient was given, hasOpened says whether they have even looked, and lastRemindedAt when they were last nudged — enough to decide who needs a reminder.
Two more recipient states appear in reports of transactions that have moved further: completed for somebody who has acted, and superseded for a member of a group whose action a colleague already covered.
A transaction that ended as a whole — cancelled, rejected or expired — says so in the row's status, not in each recipient. The recipients keep the state their own participation reached.
Complete example
Summarizing last month's agreements, 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 AUTH_HEADER = { "Authorization": `Bearer ${API_KEY}` };
const filters = new URLSearchParams({
createdAfter: "2026-05-01T00:00:00.000Z",
createdBefore: "2026-06-01T00:00:00.000Z",
category: "Contract",
limit: "100"
});
const rows = [];
let query = filters.toString();
do {
const res = await fetch(`${BASE_URL}/reports?${query}`, { headers: AUTH_HEADER });
if (!res.ok) throw new Error(`Report failed: ${res.status}`); // error handling kept minimal for brevity
const page = await res.json();
rows.push(...page.results);
// the cursor already carries the filters, so it travels on its own
query = (page.pagination.nextCursor === null) ? null : `cursor=${page.pagination.nextCursor}`;
} while (query !== null);
const finished = rows.filter((row) => row.status === "completed");
const waiting = rows.filter((row) => row.pendingRecipientCount > 0);
console.log(`${rows.length} agreements, ${finished.length} signed, ${waiting.length} waiting on somebody`);
Next steps
-
Read the same activity as aggregated numbers instead of rows — Read a dashboard's data.
-
Take the documents themselves out, not just the figures — Export documents as archives.
-
Follow who did what across the whole organization — Search the audit logs.