# Search everything

Source: https://workroad.app/docs/api/search

`GET https://workroad.app/api/v1/search`

Finds notes (and their headings), lecture recordings, note files, study sets and their terms, tasks, timetables, copilot conversations and practice tests by their words, the same search as the app's command menu. Use it to turn something the user names ("my mitosis notes", "the Spanish verbs set") into an id, then read or change the record with its own tool. Hits come best match first, 20 a page, and only for the types this key may read: `note` and `recording` need `notes:view`, `file` `files:view`, `set` and `term` `sets:view`, `task` `tasks:view`, `schedule` `schedule:view`, `conversation` `copilot:use`, `test` `tests:view`. Without `settings:view`, a copilot conversation that read or proposed the user's settings is skipped. Shares the app's search limit of 60 a minute per user.

- MCP tool: `search` on `https://workroad.app/api/mcp`
- Required scopes: notes:view or files:view or sets:view or tasks:view or schedule:view or copilot:use or tests:view
- Test mode: `GET https://workroad.app/api/test/v1/search` checks the request and returns the example response without changing anything.
- Next: read a hit with its own tool, such as `getNote` with the hit's `id` as `noteId`.

## Query parameters

- `q`: string, required, 1-200 characters. Words to find, 1-200 characters. Matches whole words and prefixes, ignores accents and case, and tolerates a typo in titles. `"exact phrase"` and `-word` work as in the app.
- `types`: string, up to 200 characters. Comma-separated record types to search: `note`, `recording`, `file`, `set`, `term`, `task`, `schedule`, `conversation`, `test`. Omit for every type the key may read.
- `subjectId`: string (uuid). Only records filed under this subject.
- `semantic`: one of `"true"`, `"false"`. `true` also matches by meaning (hybrid search), for questions like `how do cells make energy`. Defaults to `false`, keyword search only.
- `cursor`: string or null, up to 512 characters. Opaque. Pass `nextCursor` from the previous page unchanged.

## Response

`200` OK

- `data`: array of objects, required
  - `type`: one of `"note"`, `"recording"`, `"file"`, `"set"`, `"term"`, `"task"`, `"schedule"`, `"conversation"`, `"test"`, required. What kind of record matched.
  - `id`: string (uuid), required. The record's id: pass it to the matching get tool, such as `getNote` for a `note` or `getTask` for a `task`. For a `conversation` it is the message id.
  - `parentId`: string or null, required. What the record belongs to: the note of a `recording` or `file`, the study set of a `term`, the copilot conversation of a `conversation` message. Null otherwise.
  - `title`: string, required
  - `snippet`: string, required. The matching passage, shortened.
  - `task`: object or null, required. A task's status and due day. Null for other types.
    - `status`: one of `"done"`, `"open"`, `"overdue"`, required
    - `dueDate`: string or null, required. The due day (YYYY-MM-DD) in the user's time zone.
  - `url`: string, required. Where the record opens in the app, at the match when it can.
  - `updatedAt`: string (date-time), required. When the record last changed.
- `nextCursor`: string or null, required. Opaque. Pass as `cursor` with the same query for the next 20 hits. Null on the last page.
- `total`: integer, required. Hits across every page.
- `mode`: one of `"keyword"`, `"hybrid"`, required. `hybrid` when meaning matches were added. A `semantic` search falls back to `keyword` when embeddings are unavailable.

### Example

```json
{
  "data": [
    {
      "type": "term",
      "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
      "parentId": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "title": "Spanish verbs",
      "snippet": "tener (yo): tuve",
      "task": null,
      "url": "https://workroad.app/set/get/7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b?element=2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
      "updatedAt": "2026-02-01T12:00:00.000Z"
    }
  ],
  "nextCursor": null,
  "total": 1,
  "mode": "keyword"
}
```

## Errors

Every error has the body `{ "error": { "code", "message", "details" } }`.

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `403`: `forbidden`: `types` names a type this key may not read. `details.types` lists them.
- `404`: No endpoint exists at this path.
- `405`: Method not allowed. Use a method from the Allow header.
- `429`: `rate_limited`: 60 searches a minute per user. Wait `Retry-After`. Rate limit exceeded. Wait for Retry-After before retrying. The rate-limit headers are absent when the key's own limiter rejected the request before the per-endpoint bucket check ran.
- `500`: Internal server error
