Skip to content

Search from your backend

This guide shows a search from your backend with the Node client, page by page. It is for backend developers building a search box or a filtered list; what search matches is on Search, every parameter on Search query.

Machine scope (any of): files:managefiles:read
brochures.ts
import type { SearchHit } from '@central-code-station/ccs-files-client';
import { files } from './files-client';
// Every PDF tagged 2026 under a folder and its subfolders, newest first, page by page.
export async function brochures(folderId: string): Promise<SearchHit[]> {
const hits: SearchHit[] = [];
let after: string | undefined;
do {
const page = await files.search({
q: 'brochure', // whole words (name, title, description, tags, custom values); else names starting with it
in: ['name', 'keywords'],
kind: ['pdf'],
tags: ['2026'],
folder_id: folderId,
recursive: true,
custom: { campaign: 'spring' }, // exact match on your own metadata
sort: 'updated_at',
order: 'desc',
after,
limit: 100,
});
hits.push(...page.items);
after = page.next_cursor ?? undefined;
} while (after);
return hits;
}

files.search(query) sends GET P/search. List parameters (kind, tags, status, …) take arrays; custom takes an object and becomes custom.<key>=<value>. The answer is { items, next_cursor, total, provider }; each item is a file, with highlights when q matched.

Add content to in to match the text of PDFs, Office and OpenDocument files, text, HTML and JSON files; each hit that matched there carries a content highlight. See Searching inside documents.

Your machine client searches the whole application. To show a user only what they may see, either:

  • let the user’s app call search with the user’s own token (User tokens): the service applies the user’s permissions inside the query, so paging and total are exact; or
  • filter the hits in your backend with files.files.effectivePermissionsBatch(userId, ids) before answering. Paging is then yours to manage.
const { items } = await files.tags({ prefix: 'be', limit: 10 }); // [{ tag: 'beach', count: 42 }, ...]
const departments = await files.taxonomies.get('departments'); // { values: [{ value, label, active }], enforced }

GET P/tags answers the application’s tags with their counts, for autocomplete. GET P/taxonomies/:name answers the managed list of categories or departments; setting one needs files:config.manage (PUT P/taxonomies/:name).

Code Cause
invalid_request An unknown sort, sort=relevance without q, more than five custom. filters, an in other than name, keywords, content
invalid_cursor after is not the next_cursor of the same search
too_many_scopes A user token whose access spans too many folders: add folder_id
rate_limited More than 120 per minute searches