PSA/docs/api/search.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

113 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Unified Full-Text Search
The `GET /api/v1/search` endpoint searches across all indexed business records in a single request, returning results from any combination of tickets, clients, contacts, projects, assets, invoices, contracts, documents, knowledge-base articles, and more — filtered by the caller's permissions and scoped to the caller's tenant.
Before this endpoint, callers had to query each resource's dedicated `/search` route separately and merge results themselves. The unified endpoint handles that fan-out internally.
## Authentication
Include your API key in the `x-api-key` header. No separate search permission is required — any valid API key may call this endpoint. Client-portal API keys are automatically scoped to their own client's records.
```
GET /api/v1/search
x-api-key: <your-api-key>
```
## Query parameters
| Parameter | Required | Type | Description |
|-----------|----------|------|-------------|
| `query` | Yes | string (1200 chars) | Full-text query. Supports `OR` for alternatives (e.g. `laptop OR workstation`). |
| `types` | No | string | Comma-separated object types to include. Omit to search every type the caller can read. See [Supported types](#supported-types). |
| `limit` | No | integer (1100) | Maximum results per page. Defaults to 30. |
| `cursor` | No | string | Opaque pagination cursor from a prior response's `nextCursor`. |
| `sort` | No | `relevance` \| `recent` | Ordering. `relevance` (default) ranks by full-text score; `recent` orders by last-updated timestamp. |
## Access control
Results pass two filters before being returned:
1. **Per-type permission gate** — Any object type the caller's role cannot read is silently excluded. For example, an API key without `invoice:read` receives no invoice, invoice item, or invoice annotation results.
2. **Per-row ACL check** — Each candidate result is verified against the per-row access-control record in the search index. Only records the user could see in-app are returned.
## Response
```json
{
"data": {
"results": [
{
"type": "ticket",
"id": "9a4b...",
"title": "Network outage at main office",
"subtitle": "Acme Corp · Open",
"snippet": "...the <mark>router</mark> stopped responding...",
"url": "/tickets/9a4b...",
"score": 0.91,
"updatedAt": "2026-05-28T10:15:00Z"
}
],
"groups": {
"ticket": 4,
"asset": 2
},
"totalCount": 6,
"nextCursor": "eyJ..."
}
}
```
### Response fields
| Field | Type | Description |
|-------|------|-------------|
| `results` | array | Matched records for this page, ordered by `sort`. |
| `results[].type` | string | Object type (e.g. `ticket`, `client`, `asset`). |
| `results[].id` | string | Record identifier within its type. |
| `results[].parentId` | string? | Parent record identifier for nested types (e.g. the ticket ID for a `ticket_comment`). |
| `results[].title` | string | Primary display label. |
| `results[].subtitle` | string? | Secondary context line. |
| `results[].snippet` | string? | Matched-text excerpt with `<mark>` tags around highlighted terms. |
| `results[].url` | string | Relative in-app URL for the record. |
| `results[].score` | number | Full-text relevance score; higher is more relevant. |
| `results[].updatedAt` | string (ISO 8601) | Last-updated timestamp of the source record. |
| `groups` | object | Per-type total match counts across all pages. |
| `totalCount` | integer | Total matches across all permitted types. |
| `nextCursor` | string? | Cursor for the next page; absent when all results fit in the current page. |
## Pagination
The endpoint uses cursor-based pagination. When a response includes `nextCursor`, pass it as the `cursor` parameter on the next call with the same `query`, `types`, and `sort` values. The `groups` and `totalCount` fields always reflect the full result set, not just the current page.
## Rate limiting
Subject to the standard API rate limit: 120-request burst, 60 requests per minute sustained. See [API Rate Limiting and Webhooks](api-rate-limiting-and-webhooks.md).
## Supported types
Pass any comma-separated subset of these values in the `types` parameter:
`client`, `contact`, `user`, `ticket`, `ticket_comment`, `project`, `project_phase`, `project_task`, `project_task_comment`, `asset`, `invoice`, `invoice_item`, `invoice_annotation`, `contract`, `client_contract`, `document`, `kb_article`, `service_catalog`, `service_request_submission`, `service_request_definition`, `workflow_task`, `interaction`, `schedule_entry`, `time_entry`, `board`, `category`, `tag`, `status`
Types the caller's role cannot read are automatically excluded even if listed explicitly.
## Examples
**Search across all types:**
```bash
curl "https://algapsa.com/api/v1/search?query=network+outage" \
-H "x-api-key: $ALGA_API_KEY"
```
**Restrict to tickets and assets, sort by recency:**
```bash
curl "https://algapsa.com/api/v1/search?query=router&types=ticket,asset&sort=recent&limit=20" \
-H "x-api-key: $ALGA_API_KEY"
```
**Fetch the next page:**
```bash
curl "https://algapsa.com/api/v1/search?query=router&types=ticket,asset&sort=recent&limit=20&cursor=$NEXT_CURSOR" \
-H "x-api-key: $ALGA_API_KEY"
```