# Storage and workspace APIs

> Reference: Document, channel, project, call, CRM, soup, pin, history, permissions, properties, and search endpoints.

- Repository: macro-inc/macro
- GitHub: https://github.com/macro-inc/macro
- Human docs: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e
- Complete Markdown: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e/llms-full.txt

## Source Files

- `js/app/packages/service-clients/service-storage/openapi.json`
- `js/app/packages/service-clients/service-storage/client.ts`
- `rust/cloud-storage/document_storage_service/src/openapi.rs`
- `js/app/packages/service-clients/service-properties/openapi.json`
- `js/app/packages/service-clients/service-properties/client.ts`
- `js/app/packages/service-clients/service-search/openapi.json`
- `js/app/packages/service-clients/service-search/client.ts`

---

---
title: "Storage and workspace APIs"
description: "Reference: Document, channel, project, call, CRM, soup, pin, history, permissions, properties, and search endpoints."
---

Macro’s workspace API surface is served primarily by Document Storage Service (DSS), with `/properties` and `/search` mounted on the same `document-storage-service` host and consumed from TypeScript clients that wrap `fetchWithToken` and return `neverthrow` `Result` values.

## Runtime and client boundaries

| Surface | TypeScript entry point | Host | Server mount |
|---|---|---:|---|
| Documents, channels, projects, soup, pins, history, permissions | `js/app/packages/service-clients/service-storage/client.ts` | `SERVER_HOSTS['document-storage-service']` | DSS root router |
| Calls | `js/app/packages/service-clients/service-call/client.ts` plus storage schemas | same DSS host | `/call` |
| Properties | `js/app/packages/service-clients/service-properties/client.ts` | same DSS host | `/properties` |
| Search | `js/app/packages/service-clients/service-search/client.ts` | same DSS host | `/search` |

The Rust OpenAPI JSON for DSS is produced from `rust/cloud-storage/document_storage_service/src/openapi.rs`, which prints the `utoipa` `ApiDoc`. The checked-in frontend OpenAPI snapshots under `js/app/packages/service-clients/*/openapi.json` and generated schema directories provide the wire types used by the clients.

<Note>
Client methods return `Result<T, ResultError[]>`; callers should branch on `isOk()` / `isErr()` or use the project’s query helpers rather than assuming failed HTTP responses throw.
</Note>

## Documents

### Core document operations

| Operation | Method and path | Client method | Notes |
|---|---|---|---|
| List recent user documents | `GET /documents?limit&offset` | `getUserDocuments` | Client maps `data.documents`, `total`, and `next_offset` to `nextOffset`. |
| Create upload-backed document | `POST /documents` | `createDocument` | Returns metadata plus S3 presigned upload URL, content type, and optional file type. |
| Create markdown document | `POST /documents/create_markdown` | `createMarkdownDocument` | Backend initializes sync-service content and returns `documentId`. |
| Create task document | `POST /documents/create_task` | `createTask` | Creates task document, initializes markdown content, and attaches task properties. |
| Get metadata | `GET /documents/{document_id}` or `/{version}` | `getDocumentMetadata` | Client retries with exponential delay up to five tries. |
| Edit metadata/share settings | `PATCH /documents/{document_id}` | `editDocument` | Used for name/project/share-permission edits. |
| Save PDF modification data | `PUT /documents/{document_id}` | `pdfSave` | Client validates modification data and response metadata with Zod. |
| Simple file/text save | `PUT /documents/{document_id}/simple_save` | `simpleSave`, `simpleSaveText` | Uses `FormData` upload body. |
| Soft delete | `DELETE /documents/{document_id}` | `deleteDocument` | Returns success data. |
| Permanent delete | `DELETE /documents/{document_id}/permanent` | `permanentlyDeleteDocument` | Removes a soft-deleted document permanently. |
| Restore soft delete | `PUT /documents/{document_id}/revert_delete` | `revertDocumentDelete` | Reverts document deletion. |
| Copy document | `POST /documents/{document_id}/copy?version_id=` | `copyDocument` | Can copy a specific document version. |
| Export | `GET /documents/{document_id}/export` | `exportDocument` | Returns export payload. |
| Batch previews | `POST /documents/preview` | `getBatchDocumentPreviews` | Body: `{ "document_ids": string[] }`. |
| List searchable docs | `GET /documents/list` | `listDocuments` | Used to populate document search surfaces. |

Creation and copy paths show the file-limit paywall when the returned error message includes `403`.

### Locations and document bytes

| Operation | Method and path | Client method | Cache behavior |
|---|---|---|---|
| Writer part URLs | `GET /documents/{uuid}/location?document_version_id=` | `getWriterPartUrls` | Cached for 14 minutes. |
| Document location v3 | `GET /documents/{document_id}/location_v3?get_converted_docx_url&document_version_id=` | `getDocumentLocation` | Cached for 14 minutes. |
| DOCX expanded file | metadata + writer parts | `getDocxFile` | Cached for 10 seconds; fails if file type is not `docx`. |
| Text document | metadata + location + presigned fetch | `getTextDocument` | Cached for 2 seconds; requires a `presignedUrl` location. |
| Binary document | metadata + location | `getBinaryDocument` | Returns `blobUrl` from the presigned URL. |

Presigned URL caching uses a 14-minute client lifetime because the server expiration is treated as 15 minutes.

### Processing and task helpers

| Operation | Path | Client method |
|---|---|---|
| Document processing result | `GET /documents/{document_id}/processing` | `getDocumentProcessingResult` |
| Job processing result | `GET /documents/{document_id}/processing/{job_id}` | `getJobProcessingResult` |
| Task short ID | `GET /documents/{document_id}/short_id` | `getDocumentShortId` |
| Task branch name | `GET /documents/{document_id}/branch_name` | `getDocumentBranchName` |
| GitHub pull requests | `GET /documents/{document_id}/github_prs` | `getDocumentGithubPullRequests` |
| Wake sync service | `POST /sync_service/wakeup` | `bulkWakeupSyncServiceDocuments` |

Processing helpers currently validate `PREPROCESS` results against `CoParseSchema`; missing or invalid JSON returns `INVALID_RESPONSE`.

## Annotations

The `storageServiceClient.annotations` namespace wraps document comments and anchors.

| Operation | Path |
|---|---|
| List comments | `GET /annotations/comments/document/{document_id}` |
| List anchors | `GET /annotations/anchors/document/{document_id}` |
| Create comment | `POST /annotations/comments/document/{document_id}` |
| Create unthreaded anchor | `POST /annotations/anchors/document/{document_id}` |
| Edit comment | `PATCH /annotations/comments/comment/{comment_id}` |
| Edit anchor | `PATCH /annotations/anchors` |
| Delete comment | `DELETE /annotations/comments/comment/{comment_id}` |
| Delete unthreaded anchor | `DELETE /annotations/anchors` |

## Channels

### Channel lifecycle

| Operation | Method and path | Client method |
|---|---|---|
| Create channel | `POST /channels` | `createChannel` |
| List channels | `GET /comms/channels` | `getChannels` |
| Get or create DM | `POST /channels/get_or_create_dm` | `getOrCreateDirectMessage` |
| Get or create private channel | `POST /channels/get_or_create_private` | `getOrCreatePrivateChannel` |
| Rename/update channel | `PATCH /channels/{channel_id}` | `patchChannel` |
| Delete channel | `DELETE /channels/{channel_id}` | `deleteChannel` |
| Batch previews | `POST /channels/preview` | `getBatchChannelPreviews` |
| Join / leave | `POST /channels/{channel_id}/join`, `/leave` | `joinChannel`, `leaveChannel` |

`getChannels` still targets `/comms/channels` on the DSS host; the source comment says it should move to `/channels` when the comms teardown finishes.

### Messages, replies, reactions, attachments

| Operation | Method and path | Client method |
|---|---|---|
| Send message | `POST /channels/{channel_id}/message` | `postMessage` |
| Edit message | `PATCH /channels/{channel_id}/message/{message_id}` | `patchMessage` |
| Delete message | `DELETE /channels/{channel_id}/message/{message_id}?nonce=` | `deleteMessage` |
| Add reaction | `POST /channels/{channel_id}/reaction` | `postReaction` |
| Typing update | `POST /channels/{channel_id}/typing` | `postTypingUpdate` |
| Page messages | `GET /channels/{channel_id}/messages?limit&cursor&previous_cursor&load_around_message_id` | `getChannelMessages` |
| Filter messages | `POST /channels/{channel_id}/messages?limit=` | `postChannelMessages` |
| Thread replies | `GET /channels/{channel_id}/messages/{message_id}/replies` | `getThreadReplies` |
| Message context | `GET /channels/{channel_id}/messages/{message_id}/context?before&after` | `getMessageWithContext` |
| Resolve message | `GET /channels/{channel_id}/messages/{message_id}/resolve` | `resolveChannelMessage` |
| Attachments page | `GET /channels/{channel_id}/attachments?limit&cursor&attachment_type` | `getChannelAttachments` |
| Participants | `GET/POST/DELETE /channels/{channel_id}/participants` | `getChannelParticipants`, `addParticipantsToChannel`, `removeParticipantsFromChannel` |
| Entity mentions | `POST /channels/mentions`, `DELETE /channels/mentions/{mention_id}` | `createEntityMention`, `deleteEntityMention` |
| Attachment references | `GET /channels/attachments/{entity_type}/{entity_id}/references` | `attachmentReferences` |
| Channel activity | `GET/POST /channels/activity` | `getActivity`, `postActivity` |

Message posting deduplicates `mentions` before sending. Several channel mutation methods accept a `nonce` and forward it in the JSON body or query string.

## Calls

The call client is a separate wrapper over DSS `/call` endpoints.

| Operation | Method and path | Client method |
|---|---|---|
| Get or create call token | `GET /call/{channel_id}` | `getOrCreateCall` |
| Leave or end call | `DELETE /call/{channel_id}` | `leaveCall` |
| Check active call | `GET /call/{channel_id}/active` | `checkActiveCall` |
| Get call record | `GET /call/record/{call_id}` | `getCallRecord` |
| Edit call record | `PATCH /call/record/{call_id}` | `editCallRecord` |
| Delete call record | `DELETE /call/record/{call_id}` | `deleteCallRecord` |
| Toggle team sharing | `POST /call/record/{call_id}/share-with-team/toggle` | `toggleShareWithTeam` |
| Batch previews | `POST /call/record/preview` | `storageServiceClient.getBatchCallPreviews` |
| Edit transcript | `PATCH /call/record/{call_id}/transcript` | no storage wrapper shown |
| Webhook ingest | `POST /call/webhook` | server route |
| Internal transcript ingest | `POST /call/{channel_id}/transcript` | internal agent route |

`checkActiveCall` treats an empty 204-style response as `null`. `toggleShareWithTeam` returns a primitive JSON boolean at runtime, so the client casts through an object type only to satisfy `fetchWithToken`’s generic constraint.

## Projects

| Operation | Method and path | Client method |
|---|---|---|
| List projects | `GET /projects` | `projects.getAll<ANSWER>---
title: "Storage and workspace APIs"
description: "Reference: Document, channel, project, call, CRM, soup, pin, history, permissions, properties, and search endpoints."
---

Macro’s workspace API surface is mounted on the document-storage-service host and consumed by TypeScript clients that wrap authenticated `fetchWithToken` calls in `neverthrow` `Result` values. Storage, properties, and search are separate client packages, but the browser-facing host is `SERVER_HOSTS['document-storage-service']`.

## Implementation surface

| Surface | Client entry point | Server mount | Spec artifact |
| --- | --- | --- | --- |
| Storage and workspace | `storageServiceClient` | `/documents`, `/channels`, `/projects`, `/items`, `/pins`, `/history`, `/crm`, `/call` | `service-storage/openapi.json` |
| Calls | `callServiceClient` | `/call` | storage OpenAPI schemas |
| Properties | `propertiesServiceClient` | `/properties` | `service-properties/openapi.json` |
| Search | `searchClient` | `/search` | `service-search/openapi.json` plus Rust router routes |

<Note>
The storage OpenAPI is generated from the Rust `utoipa` document. The `openapi.rs` binary prints `api::swagger::ApiDoc::openapi()` as JSON.
</Note>

## Transport and response conventions

- All handwritten clients use `fetchWithToken`, so callers receive `Result<T, ResultError[]>`.
- `storageServiceClient`, `propertiesServiceClient`, and `searchClient` all target the document-storage-service host.
- Most JSON bodies are serialized with `JSON.stringify`; file uploads use `FormData` or direct presigned URL `PUT`.
- Document permission token creation uses `SYNC_PERMISSION_TOKEN_DSS_HOST` instead of the normal DSS host so the token is signed by the DSS instance compatible with the sync service secret.
- Generated response wrappers commonly use `{ error, data }`; client methods often unwrap to the useful payload.

```ts
import { storageServiceClient } from '@service-storage/client';
import { propertiesServiceClient } from '@service-properties/client';
import { searchClient } from '@service-search/client';

const doc = await storageServiceClient.getDocumentMetadata({
  documentId: 'doc-id',
});

const props = await propertiesServiceClient.getEntityProperties({
  entity_type: 'DOCUMENT',
  entity_id: 'doc-id',
  query: { include_metadata: true },
});

const results = await searchClient.search({
  params: { page_size: 10 },
  request: {
    query: 'roadmap',
    match_type: 'partial',
    search_on: 'name_content',
  },
});
```

## Documents

| Operation | Endpoint | Client method | Notes |
| --- | --- | --- | --- |
| List recent documents | `GET /documents?limit=&offset=` | `getUserDocuments` | Returns documents, total, and next offset. |
| Create upload-backed document | `POST /documents` | `createDocument` | Returns metadata, `presignedUrl`, `contentType`, and optional `fileType`; client triggers file-limit paywall on 403. |
| Create markdown document | `POST /documents/create_markdown` | `createMarkdownDocument` | Backend initializes sync-service content. |
| Create task | `POST /documents/create_task` | `createTask` | Creates task document, properties, and initialized markdown content. |
| Get metadata | `GET /documents/{document_id}` or `/{version}` | `getDocumentMetadata` | Client retries metadata fetch up to 5 times with exponential delay. |
| Edit metadata/share | `PATCH /documents/{document_id}` | `editDocument` | Supports name/project/share-permission edits; moving requires access to target project. |
| Save PDF state | `PUT /documents/{document_id}` | `pdfSave` | Validates server modification-data schema before sending. |
| Save simple file/text | `PUT /documents/{document_id}/simple_save` | `simpleSave`, `simpleSaveText` | Uses `FormData` file upload. |
| Copy document | `POST /documents/{document_id}/copy?version_id=` | `copyDocument` | Creates a new document without re-uploading source content. |
| Soft delete | `DELETE /documents/{document_id}` | `deleteDocument` | Owner-only soft delete. |
| Permanent delete | `DELETE /documents/{document_id}/permanent` | `permanentlyDeleteDocument` | Hard-delete path. |
| Restore delete | `PUT /documents/{document_id}/revert_delete` | `revertDocumentDelete` | Reverts soft deletion. |
| Batch preview | `POST /documents/preview` | `getBatchDocumentPreviews` | Body: `{ document_ids: string[] }`. |
| Export | `GET /documents/{document_id}/export` | `exportDocument` | Returns export metadata/payload from DSS. |
| Processing result | `GET /documents/{document_id}/processing[/job_id]` | `getDocumentProcessingResult`, `getJobProcessingResult` | Client currently validates `PREPROCESS` results with `CoParseSchema`. |
| Location | `GET /documents/{document_id}/location_v3` | `getDocumentLocation` | Adds `get_converted_docx_url` and optional `document_version_id`. Cached for 14 minutes. |
| DOCX parts | `GET /documents/{id}/location?version_id=` | `getWriterPartUrls`, `getDocxFile` | Requires version ID; cached for 14 minutes. |

### Document content helpers

`getTextDocument`, `getBinaryDocument`, and `getDocxFile` compose metadata and location APIs. They return client-side errors such as `INVALID_DOCUMENT`, `INVALID_FILETYPE`, or `NOT_FOUND` when the location payload does not contain a usable presigned URL or the resource is gone.

## Channels and messages

| Operation | Endpoint | Client method |
| --- | --- | --- |
| Create channel | `POST /channels` | `createChannel` |
| List channels | `GET /comms/channels` | `getChannels` |
| Get or create DM | `POST /channels/get_or_create_dm` | `getOrCreateDirectMessage` |
| Get or create private channel | `POST /channels/get_or_create_private` | `getOrCreatePrivateChannel` |
| Rename channel | `PATCH /channels/{channel_id}` | `patchChannel` |
| Delete channel | `DELETE /channels/{channel_id}` | `deleteChannel` |
| Send message | `POST /channels/{channel_id}/message` | `postMessage` |
| Edit message | `PATCH /channels/{channel_id}/message/{message_id}` | `patchMessage` |
| Delete message | `DELETE /channels/{channel_id}/message/{message_id}?nonce=` | `deleteMessage` |
| React | `POST /channels/{channel_id}/reaction` | `postReaction` |
| Typing state | `POST /channels/{channel_id}/typing` | `postTypingUpdate` |
| Add/remove participants | `POST` / `DELETE /channels/{channel_id}/participants` | `addParticipantsToChannel`, `removeParticipantsFromChannel` |
| Join/leave | `POST /channels/{channel_id}/join`, `/leave` | `joinChannel`, `leaveChannel` |
| Messages page | `GET /channels/{channel_id}/messages` | `getChannelMessages` |
| Filtered messages | `POST /channels/{channel_id}/messages` | `postChannelMessages` |
| Thread replies | `GET /channels/{channel_id}/messages/{message_id}/replies` | `getThreadReplies` |
| Context around message | `GET /channels/{channel_id}/messages/{message_id}/context` | `getMessageWithContext` |
| Resolve message | `GET /channels/{channel_id}/messages/{message_id}/resolve` | `resolveChannelMessage` |
| Attachments page | `GET /channels/{channel_id}/attachments` | `getChannelAttachments` |
| Participants | `GET /channels/{channel_id}/participants` | `getChannelParticipants` |
| Entity mention | `POST /channels/mentions` | `createEntityMention` |
| Delete mention | `DELETE /channels/mentions/{mention_id}` | `deleteEntityMention` |
| Attachment references | `GET /channels/attachments/{entity_type}/{entity_id}/references` | `attachmentReferences` |
| Channel activity | `GET` / `POST /channels/activity` | `getActivity`, `postActivity` |

`postMessage` de-duplicates `mentions` before sending. Message mutations can pass a `nonce`; deletes serialize it as a query parameter while sends, edits, reactions, and typing include it in the JSON body.

## Projects

| Operation | Endpoint | Client method |
| --- | --- | --- |
| List projects | `GET /projects` | `projects.getAll` |
| Get project | `GET /projects/{id}` | `projects.getProject` |
| Get pending uploads | `GET /projects/pending` | `projects.getPending` |
| Create project | `POST /projects` | `projects.create` |
| Edit project | `PATCH /projects/{id}` | `projects.edit` |
| Soft delete project | `DELETE /projects/{id}` | `projects.delete` |
| Permanent delete | `DELETE /projects/{id}/permanent` | `projects.permanentlyDelete` |
| Restore | `PUT /projects/{id}/revert_delete` | `projects.revertDelete` |
| Project content | `GET /projects/{id}/content` | `projects.getContent` |
| Share permissions | `GET /projects/{id}/permissions` | `projects.getPermissions` |
| User access level | `GET /projects/{id}/access_level` | `projects.getUserAccessLevel` |
| Batch preview | `POST /projects/preview` | `projects.getPreview` |
| Zip upload request | `POST /projects/upload_extract` | `projects.createUploadZipRequest` |

## Calls

| Operation | Endpoint | Client method |
| --- | --- | --- |
| Join or create call | `GET /call/{channel_id}` | `callServiceClient.getOrCreateCall` |
| Leave or end call | `DELETE /call/{channel_id}` | `callServiceClient.leaveCall` |
| Active call check | `GET /call/{channel_id}/active` | `callServiceClient.checkActiveCall` |
| Get record | `GET /call/record/{call_id}` | `callServiceClient.getCallRecord` |
| Edit record | `PATCH /call/record/{call_id}` | `callServiceClient.editCallRecord` |
| Delete record | `DELETE /call/record/{call_id}` | `callServiceClient.deleteCallRecord` |
| Toggle team sharing | `POST /call/record/{call_id}/share-with-team/toggle` | `callServiceClient.toggleShareWithTeam` |
| Batch preview | `POST /call/record/preview` | `storageServiceClient.getBatchCallPreviews` |
| Edit transcript speakers | `PATCH /call/record/{call_id}/transcript` | OpenAPI route |
| Provider webhook | `POST /call/webhook` | Server route |
| Internal transcript ingest | `POST /call/{channel_id}/transcript` | Internal route guarded by `x-macro-internal-call` |

`checkActiveCall` maps an empty 204-style response to `null`. `toggleShareWithTeam` returns a primitive boolean at runtime even though the generic type is constrained to object-like values.

## CRM

| Operation | Endpoint | Request body | Behavior |
| --- | --- | --- | --- |
| List company contacts | `GET /crm/companies/{company_id}/contacts` | none | Returns non-hidden contacts for a team-owned company; owned companies with no visible contacts return `[]`. |
| Toggle email sync | `PUT /crm/companies/{company_id}/email-sync` | `{ email_sync: boolean }` | Setting `false` permanently removes existing CRM contacts and contact sources. Enabling on hidden companies returns 409. |
| Toggle company hidden | `PUT /crm/companies/{company_id}/hidden` | `{ hidden: boolean }` | Hiding disables `email_sync` and clears contacts/contact sources. Unhiding does not re-enable email sync. |
| Toggle contact hidden | `PUT /crm/contacts/{contact_id}/hidden` | `{ hidden: boolean }` | Display-only opt-out for contacts. |
| List comment threads | `GET /crm/comments/{entity_type}/{entity_id}` | none | `entity_type` is `crm_company` or `crm_contact`. |
| Create comment/reply | `POST /crm/comments/{entity_type}/{entity_id}` | `{ content, threadId? }` | Without `threadId`, creates a new thread; with `threadId`, appends a reply. |
| Edit comment | `PATCH /crm/comment/{comment_id}` | `{ content }` | Returns the updated comment. |
| Delete comment | `DELETE /crm/comment/{comment_id}` | none | Soft-deletes comment; also soft-deletes thread if it was the last live comment. |

## Soup workspace feed

Soup endpoints return workspace items with `next_cursor` and a `frecency_score`. The router supports regular filters, AST filters, and grouped AST queries.

| Operation | Endpoint | Client method | Key params |
| --- | --- | --- | --- |
| Default feed | `GET /items/soup` | not wrapped directly | `expand`, `limit`, `sort_method`, `cursor` |
| Filtered feed | `POST /items/soup?cursor=` | `getSoupItems` | Typed `EntityFilters`, `email_view`, `expand`, `limit`, `sort_method` |
| AST feed | `POST /items/soup/ast?cursor=` | `getSoupAstItems` | AST filters, `email_view`, `expand`, `limit`, `sort_method` |
| Grouped AST feed | `POST /items/soup/ast/grouped?cursor=` | `getGroupedSoupAstItems` | `group_by`, optional `group_key`, optional grouped sort |

Supported feed sort values are `viewed_at`, `created_at`, `updated_at`, `viewed_updated`, and `frecency`. Grouped queries use simple sorts only and default to `viewed_updated`.

Grouped responses include:

<ResponseField name="items" type="SoupApiItem[]">Flat item page ordered by group and sort.</ResponseField>
<ResponseField name="next_cursor" type="string | null">Global cursor for the next page.</ResponseField>
<ResponseField name="groups" type="ApiGroupMeta[]">Group metadata with `key`, `label`, `display_order`, `total_count`, `page_count`, `start_index`, and per-group `next_cursor`.</ResponseField>

<Warning>
CRM-scoped soup filters require team membership. Hidden CRM company filters require admin or owner team role.
</Warning>

## Pins and history

| Operation | Endpoint | Client method | Defaults |
| --- | --- | --- | --- |
| List pins | `GET /pins?limit=&offset=` | `getPins` | Client defaults to `limit=10`, `offset=0`. |
| Add pin | `POST /pins/{pinned_item_id}` | `pinItem` | Body is `AddPinRequest` without the path `id`. |
| Remove pin | `DELETE /pins/{pinned_item_id}` | `removePin` | Body is `PinRequest` without the path `id`. |
| Reorder pins | `PATCH /pins` | `reorderPins` | Body is an array of `ReorderPinRequest`. |
| List history | `GET /history` | `getUsersHistory` | Returns `{ data: Item[] }`. |
| Upsert history item | `POST /history/{item_type}/{item_id}` | `upsertItemToUserHistory` | Performs tracking side effects. |
| Remove history item | `DELETE /history/{item_type}/{item_id}` | `removeItemFromUserHistory` | Returns success response. |

Recognized client item types include `document`, `chat`, `project`, `channel`, `email`, `channel_message`, `call`, `automation`, and `thread`. `blockNameToItemType` maps unknown block names to `document`.

## Permissions and views

| Operation | Endpoint | Client method |
| --- | --- | --- |
| Entity permission | `GET /entity/{entity_type}/{entity_id}/permissions` | Use raw DSS fetch or generated schema |
| Document share permissions | `GET /documents/{document_id}/permissions` | `getDocumentPermissions` |
| Document permission token | `POST /documents/permissions_token/{document_id}` | `permissionsTokens.createPermissionToken` |
| Validate permission token | `POST /documents/permissions_token/validate` | `permissionsTokens.validatePermissionToken` |
| Project permissions | `GET /projects/{id}/permissions` | `projects.getPermissions` |
| Project access level | `GET /projects/{id}/access_level` | `projects.getUserAccessLevel` |
| Document viewers | `GET /documents/{document_id}/views` | `getDocumentViewers` |
| Save view location | `POST /user_document_view_location/{document_id}` | `upsertDocumentViewLocation` |
| Delete view location | `DELETE /user_document_view_location/{document_id}` | `deleteDocumentViewLocation` |

<Note>
The generated OpenAPI also advertises a v2 document permissions path for the response schema. The TypeScript storage client calls the runtime route at `/documents/{document_id}/permissions`.
</Note>

## Properties

`propertiesServiceClient` is a focused client for dynamic properties attached to entities. Entity types are uppercase wire values: `CHANNEL`, `CHAT`, `COMPANY`, `DOCUMENT`, `PROJECT`, `TASK`, `THREAD`, and `USER`.

| Operation | Endpoint | Client method |
| --- | --- | --- |
| List definitions | `GET /properties/definitions` | `listProperties` |
| Create definition | `POST /properties/definitions` | `createPropertyDefinition` |
| Delete definition | `DELETE /properties/definitions/{definition_id}` | `deletePropertyDefinition` |
| List options | `GET /properties/definitions/{definition_id}/options` | `getPropertyOptions` |
| Add option | `POST /properties/definitions/{definition_id}/options` | `addPropertyOption` |
| Delete option | `DELETE /properties/definitions/{definition_id}/options/{option_id}` | `deletePropertyOption` |
| Get entity properties | `GET /properties/entities/{entity_type}/{entity_id}` | `getEntityProperties` |
| Set entity property | `PUT /properties/entities/{entity_type}/{entity_id}/{property_id}` | `setEntityProperty` |
| Delete entity property | `DELETE /properties/entity_properties/{entity_property_id}` | `deleteEntityProperty` |
| Bulk get properties | `POST /properties/entities/bulk` | `getBulkEntityProperties` |
| Mark status complete | `PATCH /properties/entities/{entity_type}/{entity_id}/status/complete` | `setPropertyStatusComplete` |

### Definition filters

<ParamField body="scope" type="'user' | 'org' | 'system' | 'all'" required>
Scope filter for property definitions.
</ParamField>

<ParamField body="include_options" type="boolean">
When set, definitions include select options.
</ParamField>

<ParamField body="for_entity_type" type="EntityType">
Filters out definitions that cannot attach to the requested entity type.
</ParamField>

### Property values

Stored property values use capitalized response variants such as `Boolean`, `Number`, `String`, `Date`, `SelectOption`, `EntityReference`, and `Link`. Set requests use lower-case action variants such as `boolean`, `number`, `string`, `date`, `select_option`, `multi_select_option`, `entity_reference`, `multi_entity_reference`, `link`, and `multi_link`.

<RequestExample>
```json title="PUT /properties/entities/TASK/task-id/property-id"
{
  "value": {
    "type": "select_option",
    "option_id": "00000000-0000-0000-0000-000000000000"
  }
}
```
</RequestExample>

Bulk property requests accept `{ entities, property_ids? }`. The public endpoint returns only entities the user can view; inaccessible entities are omitted.

## Search

`searchClient` wraps unified and channel-specific search. Both endpoints accept `page_size` and optional base64 cursor query params. Server-side validation rejects page sizes outside `0..=100` and queries shorter than 3 characters.

| Operation | Endpoint | Client method | Response |
| --- | --- | --- | --- |
| Unified search | `POST /search` | `search` | `{ results, next_cursor }` |
| Simple unified search | `POST /search/simple` | generated `simpleUnifiedSearch` | Flat, non-grouped response |
| Channel content search | `POST /search/channel` | `searchChannel` | `{ results, next_cursor, total_count }` |

Unified request shape:

<ParamField body="query" type="string" required>
Search string; must be at least 3 characters after trimming.
</ParamField>

<ParamField body="match_type" type="'exact' | 'partial' | 'regexp' | 'query'" required>
Match mode passed to the search service.
</ParamField>

<ParamField body="search_on" type="'name' | 'content' | 'name_content'">
Defaults to content when omitted.
</ParamField>

<ParamField body="filters" type="EntityFilters">
Filter bundle for calls, channels, chats, CRM companies, documents, email, foreign entities, projects, and property filters.
</ParamField>

Channel search accepts `query` or `terms`, `match_type`, optional `sort`, and channel filters. The server requires at least one `channel_id` for `/search/channel`, even though the generated filter model describes empty arrays as broadly searchable.

<RequestExample>
```json title="POST /search/channel?page_size=20"
{
  "query": "launch notes",
  "match_type": "partial",
  "sort": "message",
  "channel_ids": ["00000000-0000-0000-0000-000000000000"]
}
```
</RequestExample>

## Operational checks for API changes

- Update Rust `utoipa` route annotations and rerun OpenAPI generation when adding or changing server routes.
- Regenerate TypeScript schemas before relying on new request or response types in clients.
- Verify handwritten clients when specs lag server routes; current examples include grouped soup and channel search wrappers.
- Keep client return mapping explicit when endpoints return primitives, 204 bodies, or wrapped `{ data }` payloads.
