A search finds documents by what they are rather than by their identifier — their metadata definition, category, creation time, transaction status or the values of their metadata fields. This guide narrows the collection down with query parameters, searches on a metadata value, and applies a view somebody saved in the web app. It takes four calls.
Before you begin
-
A few documents to find. The examples work with employment agreements carrying a category and a metadata definition, created as in Store structured data on a document.
Step 1: List documents with filters
The documents collection is filtered with query parameters:
GET /documents?metadataDefinition=d_employment&category=Contract
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"total": 2,
"pagination": {
"limit": 20,
"nextCursor": null,
"offset": 0
},
"results": [
{
"id": "hL8vXe3RmQ7zBnT5wKdY",
"title": "Employment Agreement (Robert Brown)",
"category": "Contract",
"metadataDefinition": "d_employment",
...
},
{
"id": "wN5cRj8XmT2qKb7VdYgA",
"title": "Employment Agreement (Jane Doe)",
"category": "Contract",
"metadataDefinition": "d_employment",
...
}
]
}
A few things to notice:
-
Every filter you add narrows the result further — a document is returned only if it matches all of them. Repeat one parameter to widen it instead:
?category=Contract&category=Leasereturns documents in either category. -
Documents come back newest first, each one the same representation
GET /documents/{id}returns. -
totalcounts every match, not just this page.paginationdescribes the page you got.
query searches the document's title and its text content instead of a specific field — useful when your integration has a search box rather than a set of filters.
Long result sets are walked with limit and the opaque cursor rather than a growing offset — Page through collections covers both.
Step 2: Search on metadata values
The values of metadata-definition fields do not fit into query parameters, so the search endpoint takes the same filters in a request body and adds metadataFields:
POST /documents/search
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"metadataDefinition": [
"d_employment"
],
"metadataFields": {
"D_EMPLOYMENT_DEPARTMENT": "Finance"
}
}
HTTP/2 200
{
"total": 1,
"results": [
{
"id": "hL8vXe3RmQ7zBnT5wKdY",
"title": "Employment Agreement (Robert Brown)",
"metadataFields": {
"D_EMPLOYMENT_DEPARTMENT": "Finance"
},
...
}
],
...
}
Values are keyed by field identifier, exactly as when you store them. Selection, date and number fields are matched exactly; free-text fields are matched by full text.
Both endpoints search your active documents. Pass trashed to search what you have thrown away instead.
Step 3: List your saved views
A view is a set of filters saved in the Circularo web app. Your own are listed on your account:
GET /me
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"documentViews": [
{
"id": "contracts_to_send",
"name": "Contracts to send this month"
}
],
...
}
Each entry is an identifier and the name the person gave it. The filters behind it stay private to the interface — your integration applies a view rather than reading it.
Step 4: Apply the saved view
Pass the identifier as viewId:
GET /documents?viewId=contracts_to_send
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
{
"total": 2,
"results": [
{
"id": "hL8vXe3RmQ7zBnT5wKdY",
"title": "Employment Agreement (Robert Brown)",
...
},
{
"id": "wN5cRj8XmT2qKb7VdYgA",
"title": "Employment Agreement (Jane Doe)",
...
}
],
...
}
The view's filters run on the server exactly as they do in the interface — here: contracts created this month whose transaction has not started yet. Because a view already carries a complete set of filters, it is used on its own: combining it with another filter is rejected with 400, while limit and offset work as usual.
Complete example
Pulling every match of one search, page by page, 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 HEADERS = { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" };
const filters = {
metadataDefinition: [
"d_employment"
],
metadataFields: {
D_EMPLOYMENT_DEPARTMENT: "Finance"
}
};
const documents = [];
let cursor = null;
do {
// a cursor is sent on its own; the filters ride along only on the first request
const body = (cursor === null) ? { ...filters, limit: 50 } : { cursor: cursor };
const res = await fetch(`${BASE_URL}/documents/search`, { method: "POST", headers: HEADERS, body: JSON.stringify(body) });
if (!res.ok) throw new Error(`Search failed: ${res.status}`); // error handling kept minimal for brevity
const page = await res.json();
documents.push(...page.results);
cursor = page.pagination.nextCursor;
} while (cursor !== null);
console.log(`Found ${documents.length} document(s)`);
Next steps
-
Store the values you later search on — Store structured data on a document.
-
Walk large result sets safely — Page through collections.