Caret organizes your writing into a clear three-level hierarchy: Workspaces at the top, Folders nested within them, and Documents living inside folders or directly at the workspace root. Every entity is UUID-keyed, soft-deleted, and protected by Supabase Row-Level Security policies at the database layer — so access control is enforced even if application-level checks are bypassed. The Document Service is the single authoritative backend for all create, read, update, and delete operations on this hierarchy.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/arrozet/caret/llms.txt
Use this file to discover all available pages before exploring further.
Entity Hierarchy
Workspace
The top-level shared context for a team. Members have roles that are inherited by documents. Every user gets a default personal workspace on signup.
Folder
Nestable containers within a workspace. Folders can contain other folders for arbitrary depth organization.
Document
The leaf entity. A document belongs to exactly one workspace and optionally one folder. It can also be shared directly with specific users independent of workspace membership.
Workspaces
Personal vs. Shared
Every user gets a personal workspace automatically on signup. Personal workspaces are single-user by default but can be upgraded by moving documents to a shared workspace. Shared workspaces support team membership with role-based access. The editor toolbar shows a “Move to workspace” action when the current document is in a personal workspace and at least one shared workspace exists.Workspace Membership Roles
Workspace roles control what a member can do within the workspace itself. These roles are distinct from per-document roles.| Role | Permissions |
|---|---|
owner | Full control — manage members, rename, delete workspace |
admin | Manage members and workspace settings |
member | Create and edit documents in the workspace |
guest | Limited read-only access to the workspace |
member role by default.
Workspace API Endpoints
Folders
Folders provide nested organization within a workspace. There is no enforced depth limit — folders can contain other folders for as many levels as your project requires. Documents can live:- Inside a folder —
folder_idis set to the folder’s UUID - At the workspace root —
folder_idisnull
Folder API Endpoints
Documents
Document Sharing
In addition to workspace-level access, documents support direct per-document sharing with individual users. This is useful for sharing a single document with someone outside the workspace. Document-level roles are independent of workspace roles:| Role | Permissions |
|---|---|
owner | Full control — edit, share, delete |
editor | Edit document content |
commenter | Add comments; read-only for content |
viewer | Read-only |
Document API Endpoints
Document Content Storage
Document content is stored in two complementary formats in the same row:| Column | Format | Purpose |
|---|---|---|
content_json | Tiptap / ProseMirror JSON | Preserves rich text formatting, node structure, and marks for round-trip editor fidelity |
content_text | Plain text | Used for full-text search, AI embedding indexing, and the status bar’s word/character counts |
content_text for embedding generation; the editor rehydrates from content_json on load.
Document Versions
Every save can produce a version record in thedocument_versions table, which stores a snapshot of content_json and content_text at that point in time. This gives you a full content history for version management and rollback.
Version history is managed by the Document Service. The editor displays the current version and provides UI to browse and restore previous versions when available.
Soft Deletes
No content in Caret is ever hard-deleted unless explicitly required. All entities — workspaces, folders, and documents — use adeleted_at timestamp column:
- Active:
deleted_at IS NULL - Soft-deleted:
deleted_atis set to the deletion timestamp
- Accidental deletions can be recovered
- Referential integrity is preserved (foreign keys still resolve)
- Audit trails remain intact
UUID Primary Keys
All entities use UUID primary keys generated by PostgreSQL’sgen_random_uuid() function:
- No sequential ID enumeration attacks
- Safe distributed generation without coordination
- Consistent key format across all services
Row-Level Security
Access control in Caret is enforced at two layers:Application layer
The Document Service validates the authenticated user’s role before executing any create, read, update, or delete operation. Unauthorized requests receive
403 Forbidden before touching the database.How RLS policies work in Supabase
How RLS policies work in Supabase
Row-Level Security policies in Supabase are PostgreSQL Policies are set once at the database level and apply universally, regardless of which service or client issues the query.
CREATE POLICY statements attached to each table. They evaluate against the authenticated user’s JWT claims (specifically the sub / user ID) on every query. For example, a policy on the documents table might look like:Architecture: Document Service
All workspace, folder, and document CRUD is owned by the Document Service (document-service), an Express 5 + TypeScript microservice using Drizzle ORM.
| Layer | Responsibility |
|---|---|
| Routes | Parse HTTP boundary, validate inputs, call services |
| Services | Business rules, role checks, mapping |
| Repositories | All SQL via Drizzle — no HTTP concepts |
| DTOs | Typed request/response shapes |
/api/v1/documents, /api/v1/workspaces, and /api/v1/folders traffic to the Document Service. Frontend HTTP calls always go through the gateway — never directly to the service on its internal port.